如何正确使用 Codable、CoreData 和 NSSet 关系?

How do I properly use Codable, CoreData and NSSet relationships?

我正在关注此 tutorial 以使用 Codable 实现 CoreData。一切似乎都很顺利,但我不知道如何编码我的照片列表 objects。您可以在下图中看到我的数据结构并查看我当前的代码。当我尝试如下解码 Pin class 中的照片 objects 时,出现错误:

Referencing instance method 'encode(_:forKey:)' on 'Optional' requires that 'NSSet' conform to 'Encodable'

照片+CoreDataClass.swift

import Foundation
import CoreData

@objc(Photo)
public class Photo: NSManagedObject, Codable {

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        do {
            try container.encode(id, forKey: .id)
            try container.encode(owner, forKey: .owner)
            try container.encode(server, forKey: .server)
            try container.encode(secret, forKey: .secret)
            try container.encode(title, forKey: .title)
            try container.encode(isPublic, forKey: .isPublic)
            try container.encode(isFriend, forKey: .isFriend)
            try container.encode(isFamily, forKey: .isFamily)
        }
    }

    required convenience public init(from decoder: Decoder) throws {
        guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
            let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let entity = NSEntityDescription.entity(forEntityName: "Photo", in: managedObjectContext) else {
                fatalError("Cannot decode Photo!")
        }
        self.init(entity: entity, insertInto: managedObjectContext)
        let values = try decoder.container(keyedBy: CodingKeys.self)
        do {
            id = try values.decode(Int64.self, forKey: .id)
            owner = try values.decode(String?.self, forKey: .owner)
            server = try values.decode(String?.self, forKey: .server)
            secret = try values.decode(String?.self, forKey: .secret)
            title = try values.decode(String?.self, forKey: .title)
            isPublic = try values.decode(Int16.self, forKey: .isPublic)
            isFriend = try values.decode(Int16.self, forKey: .isFriend)
            isFamily = try values.decode(Int16.self, forKey: .isFamily)
        } catch {
            print(error)
        }
    }

    enum CodingKeys: String, CodingKey {
        case id = "id"
        case owner = "owner"
        case server = "server"
        case secret = "secret"
        case title = "title"
        case isPublic = "ispublic"
        case isFriend = "isfriend"
        case isFamily = "isfamily"
    }

}

照片+CoreDataProperties.swift

import Foundation
import CoreData


extension Photo {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
        return NSFetchRequest<Photo>(entityName: "Photo")
    }

    @NSManaged public var id: Int64
    @NSManaged public var owner: String?
    @NSManaged public var secret: String?
    @NSManaged public var server: String?
    @NSManaged public var title: String?
    @NSManaged public var isPublic: Int16
    @NSManaged public var isFriend: Int16
    @NSManaged public var isFamily: Int16

}

针+CoreDataClass.swift

import Foundation
import CoreData

@objc(Pin)
public class Pin: NSManagedObject, Codable {

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
        try container.encode(photos, forKey: .photos)
    }

    required convenience public init(from decoder: Decoder) throws {
        guard let contextUserInfoKey = CodingUserInfoKey(rawValue: "context"),
            let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
            let entity = NSEntityDescription.entity(forEntityName: "Pin", in: managedObjectContext) else {
                fatalError("Could not decode Pin!")
        }
        self.init(entity: entity, insertInto: managedObjectContext)
        let values = try decoder.container(keyedBy: CodingKeys.self)
        do {
            latitude = try values.decode(Double.self, forKey: .latitude)
            longitude = try values.decode(Double.self, forKey: .longitude)
            photos = NSSet(array: try values.decode([Photo].self, forKey: .photos))
        } catch {
            print(error)
        }
    }

    enum CodingKeys: String, CodingKey {
        case latitude = "latitude"
        case longitude = "longitude"
        case photos = "photos"
    }

}

针+CoreDataProperties.swift

import Foundation
import CoreData


extension Pin {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Pin> {
        return NSFetchRequest<Pin>(entityName: "Pin")
    }

    @NSManaged public var latitude: Double
    @NSManaged public var longitude: Double
    @NSManaged public var photos: NSSet?

}

// MARK: Generated accessors for photos
extension Pin {

    @objc(addPhotosObject:)
    @NSManaged public func addToPhotos(_ value: Photo)

    @objc(removePhotosObject:)
    @NSManaged public func removeFromPhotos(_ value: Photo)

    @objc(addPhotos:)
    @NSManaged public func addToPhotos(_ values: NSSet)

    @objc(removePhotos:)
    @NSManaged public func removeFromPhotos(_ values: NSSet)

}

将照片声明为 swift 原生类型

@NSManaged var photos: Set<Photo>

在解码器中

photos = try values.decode(Set<Photo>.self, forKey: .photos)