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

时间:2017-03-11 09:30:44

标签: ios swift core-data concurrency

我已经阅读了一些博客,但我仍然对如何使用NSPersistentContainer performBackgroundTask来创建实体并保存它感到困惑。通过调用init(context moc: NSManagedObjectContext)块中的便捷方法performBackgroundTask() { (moc) in }来创建实例后,如果我检查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中对核心数据使用此功能的正确方法,我将非常感激

1 个答案:

答案 0 :(得分:11)

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

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

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

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

使用这个队列写所有内容:

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

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

起初我认为NSPersistentContainer&#39; s performBackgroundTask有一个内部队列,初始测试支持它。经过更多测试后,我发现它也可能导致合并冲突。