ios - 将 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 保存为字符串,然后将该字符串转换为数组
解决方案
将 json 数据或“整个原始数据”存储到 CoreData 中是一种不好的做法。而是将Show
自身存储为NSManagedObject
. 您可以通过将 JSON 数据转换为对象(看起来您已经在这样做),然后从它们创建 CoreData NSManagedObjects 来做到这一点。
实际上,如果从 JSON 转换数据没有问题,则无需在保存到 CoreData 之前将其转换为字符串。如果您对服务器的提取失败,您可以简单地将其存储Data
为NSData
,即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")
}
}
}
推荐阅读
- r - R包'word2vec'doc2vec函数
- sql - 使用返回 2465 的 SQL 使用 VBA 过滤表单找不到“|1”
- google-cloud-platform - 多环境 terragrunt gcp 项目的模板?
- python - 为什么 neo4j python 驱动官方文档中的代码示例不起作用?
- arm - STM32F103 clang 编译和大二进制大小
- javascript - 使用 CryptoJS 进行 OpenSSL 加密解密
- ballerina - 如何在 Ballerina 中将 int 转换为没有 stdlib 的字符串?
- node.js - 如何延迟将图像上传到 S3?
- google-sheets - 是否可以让表格将我的 REGEXEXTRACT 结果识别为日期?
- powershell - diskpart 可以带命令行参数吗?或者我可以用 powershell 伪造它们吗?