NSPersistentContainer并发用于保存到核心数据

MFA*_*MFA 9 concurrency core-data ios swift

我已经阅读了一些博客,但我仍然对如何使用NSPersistentContainer performBackgroundTask创建实体并保存它感到困惑.通过调用方便的方法创建一个实例后init(context moc: NSManagedObjectContext)performBackgroundTask() { (moc) in }块,如果我检查container.viewContext.hasChanges这个返回false,并说没有什么保存的,如果我叫节省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 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' 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 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ...} and new database row = {id = 2; ...}"
)}
Run Code Online (Sandbox Code Playgroud)

所以我没有得到并发工作,如果有人能向我解释在iOS 10中对核心数据使用此功能的正确方法,我将非常感激

Jon*_*ose 13

TL:DR:你的问题是你正在使用viewContext背景和背景上下文.您应该只以一种同步方式写入核心数据.

完整说明:如果一个对象同时从两个不同的上下文中更改,则核心数据不知道该怎么做.您可以设置mergePolicy来设置哪个更改应该获胜,但这确实不是一个好的解决方案,因为您可能会以这种方式丢失数据.很多专业人员长期处理这个问题的方式是有一个操作队列来排队写入,所以一次只进行一次写入,并且主线程上只有读取的另一个上下文.这样你就不会遇到任何合并冲突.(有关此设置的详细说明,请参阅https://vimeo.com/89370886).

使用此设置NSPersistentContainer非常简单.在核心数据管理器中创建一个NSOperationQueue

//obj-c
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

//swift
let persistentContainerQueue = OperationQueue()
persistentContainerQueue.maxConcurrentOperationCount = 1
Run Code Online (Sandbox Code Playgroud)

并使用此队列写所有内容:

// 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
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

当您调用enqueueCoreDataBlock块时,队列将确保没有合并冲突.但是,如果你写的viewContext那将打败这个设置.同样,您应该将您创建的任何其他上下文(with newBackgroundContext或with performBackgroundTask)视为readonly,因为它们也将在写入队列之外.

起初我以为 NSPersistentContainerperformBackgroundTask有一个内部队列,并初步测试支持这一点.经过更多测试后,我发现它也可能导致合并冲突.

  • @JonRose 苹果似乎不知道如何让 CoreData 堆栈不那么痛苦和混乱,哈哈 (6认同)
  • managedObjects 的所有创建和更新都应该在一个 `performBackgroundTask` 块中使用提供给该块的上下文来完成。不要忘记在结束时调用 save 。还要确保您的核心数据设置代码中有 ` self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true;`。 (2认同)
  • 我不认为该页面上的"只读"注释意味着视图上下文应仅用于读取数据.相反,它表示属性本身是只读的,您不能创建另一个上下文并将其指定为视图上下文. (2认同)