Swift:将 Codable Class 从 JSON 加载到 CoreData 正在生成所有属性为 nil 的对象
Swift: Loading Codable Class into CoreData from JSON is generating Objects with all properties to nil
背景:
我正在创建一个带有 buy/use 道具选项的游戏。我想将这些加电对象预加载到我的 CoreData 数据库中,数量为 0。想法是用户将购买加电,然后将他们拥有多少的上下文保存在数据库中。
问题:
我的 Codable 对象正在生成,其属性全部为 nil 或 0,即不采用 JSON 提供的信息。请你能帮我看看我哪里出错了。
我的 Codable 符合 Class:
import Foundation
import CoreData
class PowerUp: NSManagedObject, Codable {
enum CodingKeys: String, CodingKey {
case name
case desc
case image
case quantity
}
var name: String?
var desc: String?
var image: String?
var quantity: Double?
required convenience init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let context = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "PowerUp", in: context) else {
fatalError("Failed to decode PowerUp")
}
self.init(entity: entity, insertInto: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
self.desc = try container.decodeIfPresent(String.self, forKey: .desc)
self.image = try container.decodeIfPresent(String.self, forKey: .image)
self.quantity = try container.decodeIfPresent(Double.self, forKey: .quantity)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.name, forKey: .name)
try container.encode(self.desc, forKey: .desc)
try container.encode(self.image, forKey: .image)
try container.encode(self.quantity, forKey: .quantity)
}
}
public extension CodingUserInfoKey {
// Helper property to retrieve the context
static let context = CodingUserInfoKey(rawValue: "context")
}
我的 JSON (powerUpData.json):
[
{
"name": "Extra Time",
"desc": "Wind back the clock with an extra 30 seconds.",
"image": "sand-clock",
"quantity": 0.0
},
{
"name": "Voice Trade",
"desc": "Offload an asset to a friend for 10% more than originally paid.",
"image": "microphone",
"quantity": 0.0
}
]
我的 AppDelegate(完成解码和预加载的地方):
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
preloadPowerUps()
return true
}
// Skipped out non-edited, irrelevant AppDelegate functions for clarity...
func preloadPowerUps() {
guard let url = Bundle.main.url(forResource: "powerUpData", withExtension: "json") else { fatalError("no file") }
do {
let json = try Data(contentsOf: url)
print(json)
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.context!] = persistentContainer.viewContext
do {
let subjects = try decoder.decode([PowerUp].self, from: json)
print(subjects)
do {
try persistentContainer.viewContext.save()
} catch {
print("error")
}
} catch {
print("error")
}
} catch {
print("error")
}
}
更重要的是,在调试时,我的 PowerUp 对象似乎确实采用了我的 json 的值,但也有点不......
根据问题评论总结:
重要的是要记住 CoreData 仍然严重依赖 Objective-C。在您的示例代码中,class 上的属性虽然可以表示为 CoreData 属性,但 CoreData 并未处理实现。
您需要将 @NSManaged
属性添加到您的属性中,如下所示:
@NSManaged var name: String?
@NSManaged var desc: String?
@NSManaged var image: String?
@NSManaged var quantity: Double?
这会将它们作为动态对象公开给 Obj-C,并允许 CoreData 处理实现。这也有助于解释您的调试,因为在运行时打印语句会显示值,但保存的托管对象具有 nil 值。
背景: 我正在创建一个带有 buy/use 道具选项的游戏。我想将这些加电对象预加载到我的 CoreData 数据库中,数量为 0。想法是用户将购买加电,然后将他们拥有多少的上下文保存在数据库中。
问题: 我的 Codable 对象正在生成,其属性全部为 nil 或 0,即不采用 JSON 提供的信息。请你能帮我看看我哪里出错了。
我的 Codable 符合 Class:
import Foundation
import CoreData
class PowerUp: NSManagedObject, Codable {
enum CodingKeys: String, CodingKey {
case name
case desc
case image
case quantity
}
var name: String?
var desc: String?
var image: String?
var quantity: Double?
required convenience init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let context = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "PowerUp", in: context) else {
fatalError("Failed to decode PowerUp")
}
self.init(entity: entity, insertInto: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
self.desc = try container.decodeIfPresent(String.self, forKey: .desc)
self.image = try container.decodeIfPresent(String.self, forKey: .image)
self.quantity = try container.decodeIfPresent(Double.self, forKey: .quantity)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.name, forKey: .name)
try container.encode(self.desc, forKey: .desc)
try container.encode(self.image, forKey: .image)
try container.encode(self.quantity, forKey: .quantity)
}
}
public extension CodingUserInfoKey {
// Helper property to retrieve the context
static let context = CodingUserInfoKey(rawValue: "context")
}
我的 JSON (powerUpData.json):
[
{
"name": "Extra Time",
"desc": "Wind back the clock with an extra 30 seconds.",
"image": "sand-clock",
"quantity": 0.0
},
{
"name": "Voice Trade",
"desc": "Offload an asset to a friend for 10% more than originally paid.",
"image": "microphone",
"quantity": 0.0
}
]
我的 AppDelegate(完成解码和预加载的地方):
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
preloadPowerUps()
return true
}
// Skipped out non-edited, irrelevant AppDelegate functions for clarity...
func preloadPowerUps() {
guard let url = Bundle.main.url(forResource: "powerUpData", withExtension: "json") else { fatalError("no file") }
do {
let json = try Data(contentsOf: url)
print(json)
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.context!] = persistentContainer.viewContext
do {
let subjects = try decoder.decode([PowerUp].self, from: json)
print(subjects)
do {
try persistentContainer.viewContext.save()
} catch {
print("error")
}
} catch {
print("error")
}
} catch {
print("error")
}
}
更重要的是,在调试时,我的 PowerUp 对象似乎确实采用了我的 json 的值,但也有点不......
根据问题评论总结:
重要的是要记住 CoreData 仍然严重依赖 Objective-C。在您的示例代码中,class 上的属性虽然可以表示为 CoreData 属性,但 CoreData 并未处理实现。
您需要将 @NSManaged
属性添加到您的属性中,如下所示:
@NSManaged var name: String?
@NSManaged var desc: String?
@NSManaged var image: String?
@NSManaged var quantity: Double?
这会将它们作为动态对象公开给 Obj-C,并允许 CoreData 处理实现。这也有助于解释您的调试,因为在运行时打印语句会显示值,但保存的托管对象具有 nil 值。