使用performBackgroundTask

时间:2017-03-29 21:07:13

标签: ios swift core-data concurrency

我正在尝试使用新的核心数据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的逻辑时遇到的任何冲突应该是致命的还是被忽视?

3 个答案:

答案 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移到该块上方。