使用通用函数从云记录(或其他外部源)填充对象

Jor*_*416 7 generics protocols core-data swift swift-protocols

我正在为我的Swift应用程序构建通用API.我CoreData用于本地存储和CloudKit云同步.

为了能够在泛型函数中使用我的数据对象,我将它们组织如下(简要摘要):

  • 进入CoreData数据库的对象是NSManagedObject符合所调用协议的实例ManagedObjectProtocol,可以转换为DataObject实例
  • NSManagedObject需要进行云同步的s符合一个CloudObject允许从记录中填充对象的协议,反之亦然
  • 我在我的应用程序的图形层中使用的对象是NSObject类,它们符合DataObject允许转换为NSManagedObject实例的协议

特定类的对象.我希望这段代码看起来像这样:

for record in records {
    let context = self.persistentContainer.newBackgroundContext()
    //classForEntityName is a function in a custom extension that returns an NSManagedObject for the entityName provided. 
    //I assume here that recordType == entityName
    if let managed = self.persistentContainer.classForEntityName(record!.recordType) {
        if let cloud = managed as? CloudObject {   
            cloud.populateManagedObject(from: record!, in: context)
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

但是,这给了我几个错误:

Protocol 'CloudObject' can only be used as a generic constraint because it has Self or associated type requirements
Member 'populateManagedObject' cannot be used on value of protocol type 'CloudObject'; use a generic constraint instead
Run Code Online (Sandbox Code Playgroud)

CloudObject协议如下所示:

protocol CloudObject {
    associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol

    var recordID: CKRecordID? { get }
    var recordType: String { get }

    func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudManagedObject>
    func populateCKRecord() -> CKRecord
}
Run Code Online (Sandbox Code Playgroud)

不知何故,我需要找到一种方法,允许我CloudObject根据recordType我收到的特定类符合.我最好怎么做呢?

任何帮助将非常感激!

vad*_*ian 1

由于 CoreData 和 CloudKit 的数据格式不相关,因此您需要一种方法来有效地从 CloudKit 记录中识别 CoreData 对象,反之亦然。

我的建议是对 CloudKit 记录类型和 CoreData 实体使用相同的名称,并使用 format 的自定义记录名称(字符串)<Entity>.<identifer>Entity是记录类型/类名,标识符是具有唯一值的CoreData 属性。例如,如果有两个名为的实体Person,且Event记录名称为"Person.JohnDoe""Event.E71F87E3-E381-409E-9732-7E670D2DC11C"。如果存在 CoreData 关系,请添加更多点分隔组件来识别这些关系

为了方便起见,您可以使用辅助枚举Entity从记录创建适当的实体

enum Entity : String {
    case person = "Person"
    case event = "Event"

    init?(record : CKRecord) {
        let components = record.recordID.recordName.components(separatedBy: ".")
        self.init(rawValue: components.first!)
    }
}
Run Code Online (Sandbox Code Playgroud)

CKRecord以及从 a 为特定记录类型创建记录的扩展Entity(在我的示例中CloudManager是一个用于管理 CloudKit 内容(例如区域)的单例)

extension CKRecord {
    convenience init(entity : Entity) {
        self.init(recordType: entity.rawValue, zoneID: CloudManager.shared.zoneID)
    }

    convenience init(entity : Entity, recordID : CKRecordID) {
        self.init(recordType: entity.rawValue, recordID: recordID)
    }
}
Run Code Online (Sandbox Code Playgroud)

当您收到云记录时,提取实体和唯一标识符。然后尝试获取对应的CoreData对象。如果对象存在则更新它,如果不存在则创建一个新对象。另一方面,从具有唯一记录名称的 CoreData 对象创建新记录。您的CloudObject协议广泛适合此模式,不需要关联的类型(顺便说一下,删除它可以消除错误),但添加一个要求recordName

var recordName : String { get set }
Run Code Online (Sandbox Code Playgroud)

recordID以及从记录名称中获取 的扩展

extension CloudObject where Self : NSManagedObject {

    var recordID : CKRecordID {
        return CKRecordID(recordName: self.recordName, zoneID: CloudManager.shared.zoneID)
    }
}
Run Code Online (Sandbox Code Playgroud)