我读过一些博客,但是我仍然对如何使用 NSPersistentContainer performBackgroundTask创建实体并保存它感到困惑。 通过init(context moc: NSManagedObjectContext)在performBackgroundTask() { (moc) in }块中调用便捷方法创建实例后,如果 检查是否container.viewContext.hasChanges返回false并表示没有 任何要保存的内容,如果我调用save on moc(为此 块创建的后台MOC ),则会收到如下错误:
fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 “Could not merge changes.” UserInfo={conflictList=( “NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID ‘0xd000000000100000 ‘ with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; … }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 “Could not merge changes.” UserInfo={conflictList=( “NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID ‘0xd000000000100000 ‘ with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; …} and new database row = {id = 2; …}” )}
fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain
Code=133020 “Could not merge changes.” UserInfo={conflictList=( “NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID ‘0xd000000000100000 ‘ with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; … }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 “Could not merge changes.” UserInfo={conflictList=( “NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID ‘0xd000000000100000 ‘ with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; …} and new database row = {id = 2; …}” )}
因此,我无法使并发正常工作,如果有人可以向我解释在iOS 10中对核心数据使用此功能的正确方法,我将非常感谢
TL:DR : TL:DR:您的问题是您同时使用viewContext 和背景环境进行编写。您只应以一种 同步方式写入核心数据。
完整说明:如果在两个 不同的上下文中同时更改了一个对象,则核心数据将不知所措。您可以设置 mergePolicy来设置应赢的变更,但这并不是一个好的 解决方案,因为您可能会丢失数据。许多专家 长期以来一直在处理该问题,其方式是让操作 队列将写操作排队,因此一次仅执行一次写操作,而 在主线程上仅允许读操作拥有另一个上下文。这样,您就永远不会遇到任何 合并冲突。(有关 此设置的详细说明,请参见https://vimeo.com/89370886 )。
进行此设置NSPersistentContainer非常容易。在您的核心数据 管理器中,创建一个NSOperationQueue
// obj c - (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{ void (^blockCopy)(NSManagedObjectContext*) = [block copy]; [self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{ NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext; [context performBlockAndWait:^{ blockCopy(context); [context save:NULL]; //Don't just pass NULL here, look at the error and log it to your analytics service }]; }]]; } //swift func enqueue(block: @escaping (_ context: NSManagedObjectContext) -> Void) { persistentContainerQueue.addOperation(){ let context: NSManagedObjectContext = self.persistentContainer.newBackgroundContext() context.performAndWait{ block(context) try? context.save() //Don't just use '?' here look at the error and log it to your analytics service } } }
调用时,enqueueCoreDataBlock该块将排队,以确保 没有合并冲突。但是,如果您写入,viewContext那将 破坏此设置。同样,您应该将您 创建的任何其他上下文(使用newBackgroundContext或使用performBackgroundTask)视为 只读,因为它们也将不在编写队列中。
enqueueCoreDataBlock
viewContext
newBackgroundContext
performBackgroundTask
起初我以为NSPersistentContainer的performBackgroundTask有 内部队列,并初步测试支持这一点。经过更多测试后,我 发现它也可能导致合并冲突。
NSPersistentContainer