代码之家  ›  专栏  ›  技术社区  ›  Joris416

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

  •  7
  • Joris416  · 技术社区  · 6 年前

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

    • 进入 岩芯数据 NSManagedObject 符合称为 ManagedObjectProtocol ,从而可以转换为 DataObject 实例
    • 需要云同步的符合称为 CloudObject 它允许从记录中填充对象,反之亦然
    • 我在应用程序的图形层中使用的对象是符合 数据对象 允许转换为 被管理对象

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

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

    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
    

    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
    }
    

    不知何故,我需要找到一种方法,让我得到符合特定类 云对象 基于 recordType 我收到了。我最好怎么做?

    2 回复  |  直到 6 年前
        1
  •  1
  •   vadian    6 年前

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

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

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

    enum Entity : String {
        case person = "Person"
        case event = "Event"
    
        init?(record : CKRecord) {
            let components = record.recordID.recordName.components(separatedBy: ".")
            self.init(rawValue: components.first!)
        }
    }
    

    以及 CKRecord 从中为特定记录类型创建记录 实体 (在我的例子中 CloudManager

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

    当您收到云记录时,提取实体和唯一标识符。然后尝试获取相应的CoreData对象。如果对象存在,则更新它;如果不存在,则创建一个新对象。另一方面,从具有唯一记录名称的CoreData对象创建新记录。你的 CloudObject 协议广泛适用于这种模式,不需要关联的类型(通过删除它来消除错误),而是添加一个需求 recordName

    var recordName : String { get set }
    

    recordID

    extension CloudObject where Self : NSManagedObject {
    
        var recordID : CKRecordID {
            return CKRecordID(recordName: self.recordName, zoneID: CloudManager.shared.zoneID)
        }
    }
    
        2
  •  0
  •   user9335240    6 年前

    SWIFT不是java,SWIFT就像C++一样, associatedType 是一种写泛型的方法 protocol 而SIFFT中的泛型则意味着C++模板。

    在爪哇, ArrayList<String> 类型与 ArrayList<Integer> !!

    在SWIFT(和C++)中, Array<String> 不是 与相同类型 Array<Int>

    所以,你不能接受 Array Array<SpecificType>

    例如,苹果做了什么让你能够制作一个“类型擦除”数组?

    他们制造了 Array<T> Array<Any>

    如果你想在你的代码中嵌入这个,怎么做?

    protocol CloudObject {
        // Omitted the associatedtype (like you already done as in the replies)
        //associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
    
        var recordID: CKRecordID? { get }
        var recordType: String { get }
    
        func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<NSManagedObject & ManagedObjectProtocol>
        func populateCKRecord() -> CKRecord
    }
    

    在编译时已知协议解析的情况下,编制“通用协议”,有助于安全和性能编程

    protocol CloudObjectGeneric: CloudObject {
        // Generify it
        associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
    
        // You don't need to redefine those, those are not changed in generic form
        //var recordID: CKRecordID? { get }
        //var recordType: String { get }
        //func populateCKRecord() -> CKRecord
    
        // You need a new function, which is the generic one
        func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject>
    }
    

    populateManagedObject 每个实现中的功能

    extension CloudObjectGeneric {
        // Function like this if the generic was a parameter, would be
        // straightforward, just pass it with a cast to  indicate  you
        // are NOT CALLING THE SAME FUNCTION, you are calling it  from
        // the generic one,  but here the generic is in the return, so
        // you will need a cast in the result.
        func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject> {
    
             let generic = populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext)
             return generic as! Promise<CloudObject> // In Promises I think this
                           // will NOT work, and you need .map({$0 as! CloudObject})
        }
    }