CoreData and Codable class compiler error: 'se

2019-09-01 04:21发布

问题:

After following instructions from this answer: https://stackoverflow.com/a/46917019/6047611

I am running into the compiler error 'self.init' isn't called on all paths before returning from initializer. super.init() isn't allowed. However, calling self.init(entity: entity, insertInto: context) and then initializing all of the properties in the class somehow doesn't fully initialize the class.

I'm lost. I'm pretty new to CoreData and am not comfortable with it yet, so I'm hoping this is an issue of my own ignorance. Any ideas on how to fix this error?

import Foundation
import CoreData

class Product: NSManagedObject, Encodable, Decodable
{
    @NSManaged var mongoID:[String:String]
    @NSManaged var title:String
    @NSManaged var productID:Int
    @NSManaged var mpn:String
    @NSManaged var listPrice:Float
    @NSManaged var price:Float
    @NSManaged var uom:String
    @NSManaged var uomQty:Int
    @NSManaged var inventory:Float
    @NSManaged var minSaleQty:Int
    @NSManaged var desc:String

    @NSManaged var categories:[String]
    @NSManaged var imageURL:String
    @NSManaged var upc:String
    @NSManaged var quantity:Int
    @NSManaged var disc:Bool

    enum CodingKeys: String, CodingKey {
        case mongoID = "mongoID"
        case title = "title"
        case productID = "productID"
        case mpn = "mpn"
        case listPrice = "listPrice"
        case price = "price"
        case uom = "uom"
        case uomQty = "uomQty"
        case inventory = "inventory"
        case minSaleQty = "minSaleQty"
        case desc = "desc"
        case categories = "categories"
        case imageURL = "imageURL"
        case upc = "upc"
        case quantity = "quantity"
        case disc = "disc"
    }

    required convenience init(from decoder:Decoder) throws
    {
        guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { print("failed context get"); return }
        guard let entity = NSEntityDescription.entity(forEntityName: "Product", in: context) else { print("failed entity init"); return }


        self.init(entity: entity, insertInto: context)

        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.mongoID = try container.decodeIfPresent([String:String].self, forKey: .mongoID) ?? ["$id":"nil"]
        self.title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
        self.productID = try container.decodeIfPresent(Int.self, forKey: .productID) ?? 0
        self.mpn = try container.decodeIfPresent(String.self, forKey: .mpn) ?? ""
        self.listPrice = try container.decodeIfPresent(Float.self, forKey: .listPrice) ?? 0.0
        self.price = try container.decodeIfPresent(Float.self, forKey: .price) ?? 0.0
        self.uom = try container.decodeIfPresent(String.self, forKey: .uom) ?? ""
        self.uomQty = try container.decodeIfPresent(Int.self, forKey: .uomQty) ?? 0
        self.inventory = try container.decodeIfPresent(Float.self, forKey: .inventory) ?? 0.0
        self.minSaleQty = try container.decodeIfPresent(Int.self, forKey: .minSaleQty) ?? 0
        self.desc = try container.decodeIfPresent(String.self, forKey: .desc) ?? ""

        self.categories = try container.decodeIfPresent([String].self, forKey: .categories) ?? [""]
        self.imageURL = try container.decodeIfPresent(String.self, forKey: .imageURL) ?? ""
        self.upc = try container.decodeIfPresent(String.self, forKey: .upc) ?? ""
        self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity) ?? 0
        self.disc = try container.decodeIfPresent(Bool.self, forKey: .disc) ?? false
    }//'self.init' isn't called on all paths before returning from initializer

    public func encode(to encoder: Encoder) throws
    {

    }
}


extension CodingUserInfoKey {
    static let context = CodingUserInfoKey(rawValue: "context")
}

回答1:

This compiler error is not related to Core Data. It is caused by the two guard statements which can return before self.init is called.

In the statement below, if context is nil, the else condition will print "failed context get" and then return:

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext 
else { print("failed context get"); return }

You are attempting to return before self.init has been called. This is not allowed. Your convenience initializer must return a properly initialized object.

However, you have a way out in case either guard statement cannot be satisfied: you can throw an exception. It then becomes the responsibility of the caller to handle the exception in whatever way makes sense.

To do this, you would need to create an enum that conforms to the Error protocol, for example:

enum ProductError: Error {
    case contextMissing
    case entityCreationFailed
}

You could then rewrite the guard statements like this:

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext 
else { print("failed context get"); throw ProductError.contextMissing }

When creating Product you can do this:

let product = try? Product(from: decoder)  
//product is an optional, might be nil

Or this:

if let product = try? Product(from: decoder) {
    //product is not an optional, cannot be nil
}