0

我的应用程序使用 CoreData + CloudKit 同步。一些 CoreData 实体Item可以通过 iCloud 的共享数据库共享。该应用程序仅使用 1 NSPersistentContainer,但它具有 2 NSManagedContexts、 thevisualContext和 a backgroundContext
因此,在保存上下文期间,可能会出现两种类型的合并冲突:1)如果两个上下文都尝试Item在不同的状态下保存相同的内容,以及 2)如果我的持久容器和 iCloud 同步尝试Item在不同的状态下保存相同的内容。

Item有一个属性updatedAt,并且应用程序要求始终Item保存最后更新的版本。
出于一致性原因,我不能按属性合并。只能存储完整Item的对象,要么存储在托管上下文中,要么存储在托管上下文中,或者持久存储。
但是不能使用标准的合并策略:NSRollbackMergePolicy忽略托管上下文中的更改,并获取持久副本,同时NSOverwriteMergePolicy用托管上下文中的对象覆盖持久存储。但我必须使用Item最新的updatedAt. 因此我必须使用自定义合并策略。

很难找到任何提示如何做到这一点。我找到了两个带有演示代码的教程。最好的一本是 Florian Kugler 和 Daniel Eggert 所著的Core Data一书,其中有一个关于自定义合并策略的部分,以及此处的相关代码。另一个是 Deepika Ramesh 的帖子,其中包含代码。但是我不得不承认,我并没有完全理解这两者。但是根据他们的代码,我尝试设置自己的自定义合并策略,该策略将分配给mergePolicy两个托管上下文的属性。这里是:

import CoreData

protocol UpdateTimestampable {
    var updatedAt: Date? { get set }
}

class NewestItemMergePolicy: NSMergePolicy {
    
    init() {
        super.init(merge: .overwriteMergePolicyType)
    }

    override open func resolve(optimisticLockingConflicts list: [NSMergeConflict]) throws {
        let nonItemConflicts = list.filter({ $0.sourceObject.entity.name != Item.entityName })
        try super.resolve(optimisticLockingConflicts: nonItemConflicts)
        
        let itemConflicts = list.filter({ $0.sourceObject.entity.name == Item.entityName })
        itemConflicts.forEach { conflict in
            guard let sourceObject = conflict.sourceObject as? UpdateTimestampable else { fatalError("must be UpdateTimestampable") }
            let key = "updatedAt"
            let sourceObjectDate = sourceObject.updatedAt ?? .distantPast
            let objectDate    = conflict.objectSnapshot?[key] as? Date ?? .distantPast
            let cachedDate    = conflict.cachedSnapshot?[key] as? Date ?? .distantPast
            let persistedDate = conflict.persistedSnapshot?[key] as? Date ?? .distantPast
            let latestUpdateAt = [sourceObjectDate, objectDate, cachedDate, persistedDate].max()
            
            let persistedDateIsLatest = persistedDate == latestUpdateAt
            let sourceObj = conflict.sourceObject
            if let context = sourceObj.managedObjectContext {
                context.performAndWait { 
                    context.refresh(sourceObj, mergeChanges: !persistedDateIsLatest)
                }
            }
        }
        
        try super.resolve(optimisticLockingConflicts: itemConflicts)
    }
    
}  

我的第一个问题是这段代码是否有意义。我问这个是因为合并冲突很难测试。
具体来说,我显然必须使用 中的任何标准合并属性super.init(merge: .overwriteMergePolicyType),尽管显然哪个并不重要,因为我使用的是自定义合并冲突解决方案。

4

0 回答 0