我正在尝试使用新的核心数据API NSPersistentContainer,并且认为内部排队机制会阻止写入事务同时进行评估,详见此堆栈溢出答案NSPersistentContainer concurrency for saving to core data
许多专业人员长期处理这个问题的方式(甚至在NSPersistentContainer之前就已经这样做了)就是有一个操作队列来排队写入,所以一次只能进行一次写入,并且在主线程上只有读取的另一个上下文。这样你就不会遇到任何合并冲突。 (有关此设置的详细解释,请参阅https://vimeo.com/89370886,现在NSPersistentContainer在内部执行此操作)。 当您调用performBackgroundTask时,persistentContainer将该块排入内部串行队列。这确保没有mergeConflicts。
但是,如果我在每次迭代时使用performBackgroundTask
在紧密循环中插入多个共享关系目标的实体,则在保存上下文时会出现NSMergeConflict
错误:
let bundlePath = Bundle.main.resourceURL!
let directoryEnumerator = FileManager.default.enumerator(at: bundlePath, includingPropertiesForKeys: [URLResourceKey.isDirectoryKey, URLResourceKey.nameKey])
while let url = directoryEnumerator?.nextObject() as? URL {
if url.pathExtension == "jpeg" {
let imageData = try! Data(contentsOf: url)
DataManager.persistentContainer.performBackgroundTask { (context) in
// context.mergePolicy = NSMergePolicy.overwrite
let new = Photo(context: context)
new.name = url.lastPathComponent
new.data = imageData as NSData
let corresponding = try! context.existingObject(with: DataManager.rootFolder.objectID) as! Folder
new.parent = corresponding
try! context.save()
}
}
我在github上发布了一个示例项目来演示这个问题: https://github.com/MaximeBoulat/NSPersistentContainer_Merge_Conflict
崩溃似乎正在发生,因为多个实体正在同时为同一个父设置它们的“父”关系,这导致父级的“子”关系在并发更新中被去同步。
即使我将传入上下文的.automaticallyMergesChangesFromParent
属性设置为true,也会发生这种情况。我可以通过定义传入上下文的合并策略来防止崩溃,但这不是一个可接受的解决方案。
有没有办法配置NSPersistentContainer以正确序列化使用performBackgroundTask
API调度的更新。或者是否有一些我错过的导致这些更新相互冲突的东西?或者Apple是否提供NSPersistentContainer堆栈,期望在评估传递到performBackgroundTask
的逻辑时遇到的任何冲突应该是致命的还是被忽视?
答案 0 :(得分:2)
来自performBackgroundTask(:)
上的documentation:
每次调用此方法时,持久性容器都会创建一个新的NSManaged Object Context,其并发类型设置为private Queue Concurrency Type。然后持久容器对上下文的私有队列
上新创建的上下文执行传入的块
所以,我认为这不是你想做的事情。我想你想调用newBackgroundContext()
,将它存储在某处的属性中,并在需要序列化时使用performBlock(:)
。
答案 1 :(得分:2)
我写了你引用的答案。我错了。我已经更新了它。
我发现NSPersistentContainer
的{{1}}没有功能内部队列,可能会导致合并冲突。当我最初测试它时,它似乎确实如此,但我发现像你一样可能存在冲突。幸运的是,通过创建自己的队列来解决这个问题并不困难。我知道苹果公司发布一些破碎的东西似乎很奇怪,但事实似乎就是这样。
我很抱歉发布了不正确的信息。
答案 2 :(得分:0)
您的代码中有一个错误,DataManager的第77行在后台线程上,并调用rootFolder,然后使用viewContext。您不应在后台线程上使用viewContext。您需要在后台线程开始之前已经有了objectID,可以将rootFolder.objectID移到该块上方。