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

时间:2021-10-18 16:31:38

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:

我已经阅读了一些博客,但我仍然对如何使用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; ...}"
)}

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

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

1 个解决方案

#1


6  

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.

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

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).

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

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

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

_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

And do all writing using this queue:

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

- (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
        }];
    }]];
}

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.

当您调用enqueueCoreDataBlock时,该块将入队以确保不存在合并冲突。但是如果你写入viewContext会破坏这个设置。同样,您应该将您创建的任何其他上下文(使用newBackgroundContext或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的performBackgroundTask有一个内部队列,并且支持初始测试。经过更多测试后,我发现它也可能导致合并冲突。

#1


6  

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.

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

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).

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

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

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

_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

And do all writing using this queue:

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

- (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
        }];
    }]];
}

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.

当您调用enqueueCoreDataBlock时,该块将入队以确保不存在合并冲突。但是如果你写入viewContext会破坏这个设置。同样,您应该将您创建的任何其他上下文(使用newBackgroundContext或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的performBackgroundTask有一个内部队列,并且支持初始测试。经过更多测试后,我发现它也可能导致合并冲突。