CoreData嵌套上下文:保存上下文的正确方式是什么?

6

我正在使用嵌套上下文模式来支持多线程下的CoreData工作。 我有一个名为CoredDataManager的单例类,其中上下文的初始化如下:

self.masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.masterContext.persistentStoreCoordinator = self.persistentStoreCoordinator;

self.mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainContext.parentContext = self.masterContext;

对于从Web服务返回的每个插入操作,我使用CoreDataManager的API来获取新的托管上下文:

- (NSManagedObjectContext *)newManagedObjectContext {
    NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    workerContext.parentContext = self.mainContext;

    return workerContext;
}

它看起来像这样(PlayerView类是NSManagedObject类的子类):

[PlayerView insertIfNeededByUniqueKey:@"playerViewId" value:playerViewId inBackgroundWithCompletionBlock:^(NSManagedObjectContext *context, PlayerView *playerView) {
    playerView.playerViewId = playerViewId;
    playerView.username = playerViewDictionary[@"name"];

    [context saveContextWithCompletionBlock:^{
         //do something
    } onMainThread:NO];//block invocation on background thread
}];

saveContextWithCompletionBlock方法是在NSManagedObjectContext类别中实现的:

- (void)saveContextWithCompletionBlock:(SaveContextBlock)completionBlock onMainThread:(BOOL)onMainThread {
    __block NSError *error = nil;

    if (self.hasChanges) {
        [self performBlock:^{
            [self save:&error];

            if (error) {
                @throw [NSException exceptionWithName:NSUndefinedKeyException
                                               reason:[NSString stringWithFormat:@"Context saving error: %@\n%@\n%@", error.domain, error.description, error.userInfo]
                                             userInfo:error.userInfo];
            }

            if (completionBlock) {
                if (onMainThread && [NSThread isMainThread]) {
                    completionBlock();
                } else if (onMainThread) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completionBlock();
                    });
                } else if ([NSThread isMainThread]) {
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
                        completionBlock();
                    });
                } else {
                    completionBlock();
                }
            }
        }];
    }
}

然后,在某个阶段,我会调用CoreDataManager的方法来保存主上下文:
- (void)saveMasterContext; {
    __block NSError *error;

    [self.mainContext performBlock:^{
        [self.mainContext save:&error];
        [self treatError:error];

        [self.masterContext performBlock:^{
            [self.masterContext save:&error];
            [self treatError:error];
        }];
    }];
}

我有两个主要的类,它们都是NSManagedObject的子类 - PlayerView和Post。 PlayerView与Post之间具有一对多的关系。 PlayerView已保存并且没有问题。而Post从未被保存,我得到了以下错误:

CoreData: error: Mutating a managed object 0x17dadd80 (0x17daf930) after it has been removed from its context.

我认为问题在于上下文保存逻辑。


一个问题是这段代码是否会被执行?如果当前线程是主线程,那么就会生成一个新的线程并异步执行completionBlock()。但是如果生成的线程不是PlayerView回调所在的线程,那么在// do something处引用传入的上下文将不会在正确的线程上。 - mitrenegade
2个回答

0

首先,你遇到的错误通常发生在你创建新托管对象的上下文消失(释放)之前,你没有机会保存它。

其次,确保上下文在适当的线程中保存的最佳方法是使用performBlockperformBlockAndWait,而不是试图弄清楚上下文属于哪个线程。这是一个安全保存上下文的示例“保存”函数:

+ (BOOL)save:(NSManagedObjectContext *)context {
    __block BOOL saved = NO;
    [context performBlockAndWait: {
        NSError *error;
        saved = [context save:&error];
        if (!saved) {
            NSLog("failed to save: %@", error);
        }
    }]
    return saved;
}

关于使用嵌套的私有上下文(以主线程上下文为父级),我们的团队遇到了一些问题(无法确切地回忆起是什么问题),但我们决定监听NSManagedObjectContextDidSaveNotification并使用mergeChangesFromContextDidSaveNotification来更新上下文。

希望这可以帮助到您。


也许你遇到的问题是,如果你使用多个嵌套上下文(一个父上下文对应多个子上下文),更改只会自动合并到父上下文中,而不是同级上下文。因此,同级上下文仍需要通过通知来合并更改。 - Dalmazio

0
一篇由Bart Jacobs撰写的绝佳教程,题为从零开始的Core Data:并发,详细描述了两种方法,其中更优雅的解决方案涉及父/子托管对象上下文,包括如何正确保存上下文。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接