本文介绍了用于保存到核心数据的 NSPersistentContainer 并发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经阅读了一些关于此的博客,但我仍然对如何使用 NSPersistentContainer performBackgroundTask 创建实体并保存它感到困惑.通过调用便利方法 init(context moc: NSManagedObjectContext)performBackgroundTask() { (moc) in } 块中创建实例后,如果我检查 container.viewContext.hasChanges 这将返回 false 并表示没有什么可保存的,如果我在 moc 上调用 save(为此块创建的背景 MOC),我会收到如下错误:

致命错误:无法保存上下文:错误域=NSCocoaErrorDomain 代码=133020无法合并更改."用户信息={冲突列表=(NSManagedObject (0x1702cd3c0) 的 NSMergeConflict (0x17466c500),对象 ID 为 '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C99r/C'4旧版本 = 1 和新版本 = 2 和旧缓存行 = {id = 2; ... }致命错误:无法保存上下文:错误域 = NSCocoaErrorDomain 代码 = 133020 无法合并更改." UserInfo={conflictList=(NSManagedObject (0x1742cb980) 的 NSMergeConflict (0x170664b80),对象 ID 为 '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C397D6使用 oldVersion = 1 和 newVersion = 2 和旧缓存行 = {id = 2; ...} 和新数据库行 = {id = 2; ...}")}

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

解决方案

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

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

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

//obj-c_persistentContainerQueue = [[NSOperationQueue alloc] init];_persistentContainerQueue.maxConcurrentOperationCount = 1;//迅速让persistentContainerQueue = OperationQueue()持久容器队列.maxConcurrentOperationCount = 1

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

//对象 c- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{void (^blockCopy)(NSManagedObjectContext*) = [块复制];[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{NSManagedObjectContext* context = self.persistentContainer.newBackgroundContext;[上下文 performBlockAndWait:^{块复制(上下文);[上下文保存:NULL];//不要在这里只传递NULL,查看错误并将其记录到您的分析服务中}];}]];}//迅速func enqueue(block: @escaping (_ context: NSManagedObjectContext) -> Void) {persistentContainerQueue.addOperation(){让上下文:NSManagedObjectContext = self.persistentContainer.newBackgroundContext()上下文.performAndWait{块(上下文)尝试?context.save()//不要只使用'?'在这里查看错误并将其记录到您的分析服务中}}}

当您调用 enqueueCoreDataBlock 时,块会被排队以确保没有合并冲突.但是,如果您写入 viewContext 会破坏此设置.同样,您应该将您创建的任何其他上下文(使用 newBackgroundContext 或使用 performBackgroundTask)视为只读,因为它们也会在写入队列之外.

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

I've read some blogs on this but I'm still confused on how to use NSPersistentContainer performBackgroundTask to create an entity and save it. After creating an instance by calling convenience method init(context moc: NSManagedObjectContext) in performBackgroundTask() { (moc) in } block if I check container.viewContext.hasChanges this returns false and says there's nothing to save, if I call save on moc (background MOC created for this block) I get errors like this:

So I've failed to get the concurrency working and would really appreciate if someone could explain to me the correct way of using this feature on core data in iOS 10

解决方案

TL:DR: Your problem is that you are writing using both the viewContext and with background contexts. You should only write to core-data in one synchronous way.

Full explanation: If an object is changed at the same time from two different contexts core-data doesn't know what to do. You can set a mergePolicy to set which change should win, but that really isn't a good solution, because you can lose data that way. The way that a lot of pros have been dealing with the problem for a long time was to have an operation queue to queue the writes so there is only one write going on at a time, and have another context on the main thread only for reads. This way you never get any merge conflicts. (see https://vimeo.com/89370886#t=4223s for a great explanation on this setup).

Making this setup with NSPersistentContainer is very easy. In your core-data manager create a NSOperationQueue

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

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

And do all writing using this queue:

// 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
      }
    }
}

When you call enqueueCoreDataBlock the block is enqueued to ensures that there are no merge conflicts. But if you write to the viewContext that would defeat this setup. Likewise you should treat any other contexts that you create (with newBackgroundContext or with performBackgroundTask) as readonly because they will also be outside of the writing queue.

At first I thought that NSPersistentContainer's performBackgroundTask had an internal queue, and initial testing supported that. After more testing I saw that it could also lead to merge conflicts.

这篇关于用于保存到核心数据的 NSPersistentContainer 并发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 12:55