【问题标题】:NSPersistentContainer concurrency for saving to core dataNSPersistentContainer 并发用于保存到核心数据
【发布时间】:2017-08-01 16:40:35
【问题描述】:

我已经阅读了一些关于此的博客,但我仍然对如何使用 NSPersistentContainer performBackgroundTask 创建实体并保存它感到困惑。通过在performBackgroundTask() { (moc) in } 块中调用便捷方法init(context moc: NSManagedObjectContext) 创建实例后,如果我检查container.viewContext.hasChanges,则返回false 并说没有什么可保存的,如果我在moc 上调用save(为此块创建背景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; ...}"
)}

所以我未能让并发工作,如果有人能向我解释在 iOS 10 的核心数据上使用此功能的正确方法,我将不胜感激

【问题讨论】:

标签: ios swift core-data concurrency


【解决方案1】:

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

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

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

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

//swift
let persistentContainerQueue = OperationQueue()
persistentContainerQueue.maxConcurrentOperationCount = 1

并使用此队列进行所有写入:

// 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)视为只读,因为它们也将在写入队列之外。

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

【讨论】:

  • managedObjects 的所有创建和更新都应在 performBackgroundTask 块中使用为该块提供的上下文完成。不要忘记在结束时调用 save 。还要确保你的核心数据设置代码中有`self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true;`。
  • 我不认为该页面上的“只读”注释意味着视图上下文应该仅用于读取数据。而是说属性本身是只读的,您不能创建另一个上下文并将其分配为视图上下文。
  • 如果你要在 tableView 中显示项目,你需要一个主队列上下文,newBackgroundContext 会给你一个背景上下文。此外,保存使用newBackgroundContext 创建的上下文也会导致写入冲突。
  • 我认为 Apple 正是针对这个问题设计了父子上下文。我从未亲自处理过这个特殊问题,也从未使用过父子上下文。对于少量数据,我会通过内存变量显示数据,并仅在用户按下保存时将其保存到核心数据(使用 performBackgroundTask)。任何不使用performBackgroundTask 的写法都可能导致合并冲突。
  • @JonRose Apple 似乎不知道如何让 CoreData 堆栈不那么痛苦和混乱,哈哈
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2011-12-27
  • 1970-01-01
  • 2017-08-23
  • 2013-09-24
  • 1970-01-01
相关资源
最近更新 更多