NSPersistentContainer并发用于保存到核心数据 [英] NSPersistentContainer concurrency for saving to core data

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

问题描述

我已经阅读了一些博客,但是对于如何使用NSPersistentContainer performBackgroundTask 创建实体并保存它仍然感到困惑。在通过调用便捷方法 init(context moc:NSManagedObjectContext) performBackgroundTask(){(}} 阻止,如果我检查 container.viewContext.hasChanges ,则返回false并说没有什么可保存的,如果我在 moc (为此块创建的后台MOC)出现以下错误:

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:


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

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 :哟您的问题是您同时使用 viewContext 和背景上下文进行编写。您应该只以一种同步方式写入核心数据。

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.

完整说明:如果同时从两个不同的对象更改了对象上下文核心数据不知道该怎么办。您可以设置mergePolicy来设置应赢的变更,但这并不是一个好的解决方案,因为这样可能会丢失数据。许多专家长期以来一直在处理该问题,其方式是让操作队列将写操作排队,因此一次只进行一次写操作,而在主线程上只允许读操作有另一个上下文。这样,您就永远不会遇到任何合并冲突。 (有关此设置的详细说明,请参见 https://vimeo.com/89370886 )。

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 for a great explanation on this setup).

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

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

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

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.

起初,我认为 NSPersistentContainer performBackgroundTask 有一个内部队列,最初的测试对此提供了支持。经过更多测试之后,我发现它也可能导致合并冲突。

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并发用于保存到核心数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆