我的应用程序使用 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)
,尽管显然哪个并不重要,因为我使用的是自定义合并冲突解决方案。