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

8

我正在为我的Swift应用程序构建通用API。我使用CoreData进行本地存储,使用CloudKit进行云同步。

为了能够在通用函数中处理我的数据对象,我将它们组织如下(简要概述):

  • 进入CoreData数据库的对象是符合ManagedObjectProtocol协议的NSManagedObject实例,该协议使其可以转换为DataObject实例
  • 需要云同步的NSManagedObject符合名为CloudObject的协议,该协议允许从记录中填充对象,反之亦然
  • 我在应用程序图形层中使用的对象是符合DataObject协议的NSObject类,该协议允许将其转换为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)
        }
    }
 }

然而,这给我带来了几个错误:
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

云对象协议如下所示:
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` 找到符合 `CloudObject` 的具体类。最好的方法是什么?
非常感谢您的帮助!

1
你能发布CloudObject协议吗? - allenh
谢谢,我已经发布了! - Joris416
你需要返回 Promise<CloudManagedObject> 吗?例如,它不能是像 Promise<NSManagedObject & ManagedObjectProtocol> 这样的东西吗?实现你想要的一种方法是从你的 CloudObject 中删除 associatedtype - OOPer
2个回答

1
由于CoreData和CloudKit的数据格式没有关系,因此您需要一种有效的方法来识别从CloudKit记录中的CoreData对象以及反之亦然。
我的建议是使用相同的名称用于CloudKit记录类型和CoreData实体,并使用自定义记录名称(字符串)格式为<Entity>.<identifer>Entity是记录类型/类名,标识符是带有唯一值的CoreData属性。例如,如果有两个名为PersonEvent的实体,则记录名称为"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!)
    }
}

这是CKRecord的扩展,用于从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)
    }
}

当您收到云记录时,请提取实体和唯一标识符。然后尝试获取相应的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)
    }
}

感谢您的全面回答!我目前使用DataObejcts、ManagedObjectProtocols和CloudObjects的结构已经允许我根据记录类型识别需要哪种对象。这个问题的目的是找到一种使用通用函数来实际填充这些不同类型对象的方法。在CloudObject中删除associatedType修复了错误,从而使我能够做到这一点,谢谢! - Joris416

0

Swift不是Java,Swift像C++,associatedType是编写通用protocol的一种方式,而Swift中的泛型意味着C++模板。

在Java中,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})
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接