首页 > 解决方案 > 将 json 作为字符串保存到 CoreData 并使用字符串创建对象数组

问题描述

我正在为广播电台创建一个应用程序,我想将“显示”对象存储到一个数组中。我使用网络服务器提供 json 数据来填充数组,但我想将此 json 数据作为字符串存储到 CoreData 中,以便对数组的访问不依赖于 Internet 连接。因此,我想在应用程序启动时更新 CoreData 中的字符串,但根据存储在 CoreData 中的字符串而不是来自网络服务器的 json 数据创建一个数组。

这是我从网络服务器下载 json 数据并将其存储到字符串中的函数:

func downloadShows() {
    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 }

        let jsonAsString = self.jsonToString(json: dataResponse)

        DispatchQueue.main.async {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

            let task2 = WebServer(context: context) // Link Task & Context
            task2.showsArray = jsonAsString
            print(jsonAsString)
            (UIApplication.shared.delegate as! AppDelegate).saveContext()
        }
    }
    task.resume()

}    

func jsonToString(json: Data) -> String {
    let convertedString: String

        convertedString = String(data: json, encoding: String.Encoding.utf8)! // the data will be converted to the string
    return convertedString
}

这是从 CoreData 获取的 json 创建显示数组的函数:

    func createShowsArray () -> Array<ShowModel> {
    var array: Array<ShowModel> = Array()
    var tasks: [WebServer] = []

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    do {
        tasks = try context.fetch(WebServer.fetchRequest())
    }
    catch {
        print("Fetching Failed")
    }

    let arrayAsString: String = tasks[0].showsArray!
    print(arrayAsString)
    do {
        let data1 = arrayAsString.data(using: .utf8)!
        let decoder = JSONDecoder()
        array = try decoder.decode([ShowModel].self, from:
            data1)

    } catch let parsingError {
        print("Error", parsingError)
    }

    return array
}

但是,这不会正确地将数据加载到数组中。我在 downloadShows() 函数 (jsonAsString) 中打印了保存到 CoreData 的值,并将其作为响应:

[{“名称”:“示例显示 2”,“ID”:“2”,“描述”:“这...

但是当我在 createShowsArray() 函数 (arrayAsString) 中从 CoreData 中获取字符串时,它添加了“DOG_Radio.ShowModel”

[DOG_Radio.ShowModel(名称:“示例节目2”,ID:“2”,描述:“这个......

JSON 解码器不会将 arrayAsString 解码为实际数组。它把这个扔回去:

错误dataCorrupted(Swift.DecodingError.Context(codingPath:[],debugDescription:“给定的数据不是有效的JSON。”,基础错误:可选(错误域=NSCocoaErrorDomain Code=3840“字符1周围的值无效。” UserInfo={NSDebugDescription =字符 1 周围的值无效。})))

很抱歉这个问题很长,我只是不知道如何使用 CoreData 将 json 保存为字符串,然后将该字符串转换为数组

标签: iosarraysjsonswiftcore-data

解决方案


将 json 数据或“整个原始数据”存储到 CoreData 中是一种不好的做法。而是将Show自身存储为NSManagedObject. 您可以通过将 JSON 数据转换为对象(看起来您已经在这样做),然后从它们创建 CoreData NSManagedObjects 来做到这一点。

实际上,如果从 JSON 转换数据没有问题,则无需在保存到 CoreData 之前将其转换为字符串。如果您对服务器的提取失败,您可以简单地将其存储DataNSData,即transformable或稍后重新转换它。binary data

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

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

Codable将允许您从 JSON 转换为带有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。将核心数据堆栈创建为可以在应用程序中的任何位置访问的单例类是很常见的。我已经包含了一个基本操作:

核心数据模型的图像

数据库控制器.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: "StackOverflow")

        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(). 这里最重要的一行是

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

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

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

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")
        }

    }



}

查看层次结构


推荐阅读