简体   繁体   中英

Swift: Loading Codable Class into CoreData from JSON is generating Objects with all properties to nil

Background: I am creating a game with the option to buy/use power-ups. I want to pre-load these power-up objects into my CoreData database with a quantity of 0. The idea being that the user will buy power-ups and then the context of how many they own is saved in the database.

Problem: My Codable objects are being generated with the properties all being nil or 0, ie not taking on the information provided by the JSON. Please can you help me see where I am going wrong.

My Codable conforming 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")
}

My 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
    }
]

My AppDelegate (where the decoding and pre-loading is done):

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

What's more is that when debugging, my PowerUp objects do seem to be taking on the values of my json but also kind of not... AppDelegate 调试

To summarize from the questions comments:

It's important to remember that CoreData still relies heavily on Objective-C. In your example code, the properties on your class, although expressible as CoreData attributes, the implementation is not being handled by CoreData.

You'll need to add the @NSManaged attribute to your properties like this:

@NSManaged var name: String?
@NSManaged var desc: String?
@NSManaged var image: String?
@NSManaged var quantity: Double?

This will expose them to Obj-C as dynamic and allow CoreData to handle the implementation. This would also help to explain your debugging, in that at runtime the print statement would show values, but the saved managed object had nil values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM