且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

将JSON保存为String作为String并使用String创建对象数组

更新时间:2022-12-13 23:34:59

将json数据或整个原始数据"存储到CoreData中是一种不好的做法.而是将Show本身存储为NSManagedObject. 为此,您可以将JSON数据转换为一个对象(看起来就像您已经在做的那样),然后根据它们创建CoreData NSManagedObjects.

It's a bad practice to store json data or 'whole raw data' into CoreData. Instead Store the Show itself as a NSManagedObject. You can do this by converting the JSON data to an Object (which it looks like you are already doing), then creating CoreData NSManagedObjects from them.

实际上,如果您可以轻松地从JSON转换数据,则在保存到CoreData之前无需将其转换为字符串.您可以简单地将Data存储为NSData,即transformablebinary data,如果以后无法成功提取到服务器,则可以将其重新转换.

Realistically if you have no trouble converting the data from JSON there is no need to convert it to a string before saving to CoreData. You can simply store the Data as NSData, i.e. transformable or binary data and reconvert it later if your fetch to the server fails.

但是,从长远来看,这并不是那么可靠,并且更难处理.数据可能已损坏和/或格式错误.

However, thats not that reliable and much harder to work with in the long run. The data could be corrupt and/or malformed.

简而言之,您需要一个数据模型和一个JSON可读数据结构,您可以将其转换为数据模型以供CoreData管理.在以后要允许用户更新,删除,保存或过滤单个Show时,这将变得很重要.

In short, you need a Data Model and a JSON readable Data Structure you can Convert to your Data Model to for CoreData to manage. This will become important later when you want to allow the user to update, remove, save or filter individual Show's.

Codable将允许您使用JSONDecoder().decode(_:from:)从JSON转换为Struct.

Codable will allow you to covert from JSON to a Struct with JSONDecoder().decode(_:from:).

ShowModelCodeable.swift

import Foundation

struct ShowModelCodeable: Codable {
    var name: String?
    var description: String?
    var producer: String?
    var thumb: String?
    var live: String?
    var banner: String?
    var id: String?

    enum CodingKeys: String, CodingKey {
        case name = "Name"
        case id = "ID"
        case description = "Description"
        case producer = "Producer"
        case thumb = "Thumb"
        case live = "Live"
        case banner = "Banner"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        description = try values.decode(String.self, forKey: .description)
        producer = try values.decode(String.self, forKey: .producer)
        thumb = try values.decode(String.self, forKey: .thumb)
        live = try values.decode(String.self, forKey: .live)
        banner = try values.decode(String.self, forKey: .banner)
        id = try values.decode(String.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

    }
}

接下来,我们需要一个Core Data Stack和一个CoreData Entity.将Core Data Stack创建为Class Singleton可以在应用程序中的任何位置进行访问都是很常见的.我在基本操作中包括了一个:

Next, We'll need a Core Data Stack and a CoreData Entity. Its very common to create a Core Data Stack as a Class Singleton that can be accessed anywhere in your app. I've included one with basic operations:

DatabaseController.Swift

import Foundation
import CoreData

class DatabaseController {

    private init() {}

    //Returns the current Persistent Container for CoreData
    class func getContext () -> NSManagedObjectContext {
        return DatabaseController.persistentContainer.viewContext
    }


    static var persistentContainer: NSPersistentContainer = {
        //The container that holds both data model entities
        let container = NSPersistentContainer(name: "***")

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */

                //TODO: - Add Error Handling for Core Data

                fatalError("Unresolved error \(error), \(error.userInfo)")
            }


        })
        return container
    }()

    // MARK: - Core Data Saving support
    class func saveContext() {
        let context = self.getContext()
        if context.hasChanges {
            do {
                try context.save()
                print("Data Saved to Context")
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate.
                //You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

    /* Support for GRUD Operations */

    // GET / Fetch / Requests
    class func getAllShows() -> Array<ShowModel> {
        let all = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        var allShows = [ShowModel]()

        do {
            let fetched = try DatabaseController.getContext().fetch(all)
            allShows = fetched
        } catch {
            let nserror = error as NSError
            //TODO: Handle Error
            print(nserror.description)
        }

        return allShows
    }

    // Get Show by uuid
    class func getShowWith(uuid: String) -> ShowModel? {
        let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        requested.predicate = NSPredicate(format: "uuid == %@", uuid)

        do {
            let fetched = try DatabaseController.getContext().fetch(requested)

            //fetched is an array we need to convert it to a single object
            if (fetched.count > 1) {
                //TODO: handle duplicate records
            } else {
                return fetched.first //only use the first object..
            }
        } catch {
            let nserror = error as NSError
            //TODO: Handle error
            print(nserror.description)
        }

        return nil
    }

    // REMOVE / Delete
    class func deleteShow(with uuid: String) -> Bool {
        let success: Bool = true

        let requested = NSFetchRequest<ShowModel>(entityName: "ShowModel")
        requested.predicate = NSPredicate(format: "uuid == %@", uuid)


        do {
            let fetched = try DatabaseController.getContext().fetch(requested)
            for show in fetched {
                DatabaseController.getContext().delete(show)
            }
            return success
        } catch {
            let nserror = error as NSError
            //TODO: Handle Error
            print(nserror.description)
        }

        return !success
    }

}

// Delete ALL SHOWS From CoreData
class func deleteAllShows() {
    do {
        let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "ShowModel")
        let deleteALL = NSBatchDeleteRequest(fetchRequest: deleteFetch)

        try DatabaseController.getContext().execute(deleteALL)
        DatabaseController.saveContext()
    } catch {
        print ("There is an error in deleting records")
    }
}

最后,我们需要一种获取JSON数据并将其转换为对象,然后显示的方法.请注意,当按下更新按钮时,它将触发getDataFromServer().最重要的一行是

Finally, we need a way to get the JSON data and convert it to our Objects, then Display it. Note that when the update button is pressed, it fires getDataFromServer(). The most important line here is

self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)

正在从服务器下拉显示,并将其转换为ShowModelCodeable对象.设置newShows后,它将运行didSet中的代码,在这里您可以删除上下文中的所有对象,然后运行addNewShowsToCoreData(_:)创建要保存在上下文中的新NSManagedObjects.

The Shows are being pulled down from your Server, and converted to ShowModelCodeable Objects. Once newShows is set it will run the code in didSet, here you can delete all the Objects in the context, then run addNewShowsToCoreData(_:) to create new NSManagedObjects to be saved in the context.

我创建了一个基本的视图控制器,并以编程方式添加了tableView来管理数据.在这里,Shows是CoreData的NSManagedObject数组,而newShows是从服务器请求中获取的json编码的新对象.

I've created a basic view controller and programmatically added a tableView to manage the data. Here, Shows is your NSManagedObject array from CoreData, and newShows are new objects encoded from json that we got from the server request.

ViewController.swift

import Foundation
import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    // Properties
    var Shows:[ShowModel]?

    var newShows:[ShowModelCodeable]? {
        didSet {
            // Remove all Previous Records
            DatabaseController.deleteAllShows()
            // Add the new spots to Core Data Context
            self.addNewShowsToCoreData(self.newShows!)
            // Save them to Core Data
            DatabaseController.saveContext()
            // Reload the tableView
            self.reloadTableView()
        }
    }

    // Views
    var tableView: UITableView = {
        let v = UITableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    lazy var updateButton: UIButton = {
        let b = UIButton()
        b.translatesAutoresizingMaskIntoConstraints = false
        b.setTitle("Update", for: .normal)
        b.setTitleColor(.black, for: .normal)
        b.isEnabled = true
        b.addTarget(self, action: #selector(getDataFromServer), for: .touchUpInside)
        return b
    }()

    override func viewWillAppear(_ animated: Bool) {
        self.Shows = DatabaseController.getAllShows()
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.tableView.delegate = self
        self.tableView.dataSource = self
        self.tableView.register(ShowCell.self, forCellReuseIdentifier: ShowCell.identifier)

        self.layoutSubViews()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    //TableView -
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return DatabaseController.getAllShows().count
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        // 100
        return ShowCell.height()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: ShowCell.identifier) as! ShowCell

        self.Shows = DatabaseController.getAllShows()

        if Shows?.count != 0 {
            if let name = Shows?[indexPath.row].name {
                cell.nameLabel.text = name
            }

            if let descriptionInfo = Shows?[indexPath.row].info {
                cell.descriptionLabel.text = descriptionInfo
            }
        } else {
            print("No shows bros")
        }


        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Show the contents
        print(Shows?[indexPath.row] ?? "No Data For this Row.")

    }

    func reloadTableView() {
        DispatchQueue.main.async {
         self.tableView.reloadData()
        }
    }


    func layoutSubViews() {
        let guide = self.view.safeAreaLayoutGuide
        let spacing: CGFloat = 8

        self.view.addSubview(tableView)
        self.view.addSubview(updateButton)

        updateButton.topAnchor.constraint(equalTo: guide.topAnchor, constant: spacing).isActive = true
        updateButton.leftAnchor.constraint(equalTo: guide.leftAnchor, constant: spacing * 4).isActive = true
        updateButton.rightAnchor.constraint(equalTo: guide.rightAnchor, constant: spacing * -4).isActive = true
        updateButton.heightAnchor.constraint(equalToConstant: 55.0).isActive = true

        tableView.topAnchor.constraint(equalTo: updateButton.bottomAnchor, constant: spacing).isActive = true
        tableView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: spacing).isActive = true
    }


    @objc func getDataFromServer() {
        print("Updating...")
        let urlPath = "http://dogradioappdatabase.com/shows.php"

        guard let url = URL(string: urlPath) else {return}

        let task = URLSession.shared.dataTask(with: url) {
            (data, response, error) in
                guard let dataResponse = data, error == nil else {
                        print(error?.localizedDescription ?? "Response Error")
                return }

            do {
                self.newShows = try JSONDecoder().decode([ShowModelCodeable].self, from: dataResponse)
            } catch {
                print(error)
            }

        }

        task.resume()
    }




    func addNewShowsToCoreData(_ shows: [ShowModelCodeable]) {

        for show in shows {
            let entity = NSEntityDescription.entity(forEntityName: "ShowModel", in: DatabaseController.getContext())
            let newShow = NSManagedObject(entity: entity!, insertInto: DatabaseController.getContext())

            // Create a unique ID for the Show.
            let uuid = UUID()
            // Set the data to the entity
            newShow.setValue(show.name, forKey: "name")
            newShow.setValue(show.description, forKey: "info")
            newShow.setValue(show.producer, forKey: "producer")
            newShow.setValue(show.thumb, forKey: "thumb")
            newShow.setValue(show.live, forKey: "live")
            newShow.setValue(show.banner, forKey: "banner")
            newShow.setValue(show.id, forKey: "id")
            newShow.setValue(uuid.uuidString, forKey: "uuid")
        }

    }



}