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

时间:2014-02-27 09:55:15

标签: ios objective-c multithreading networking core-data

我使用嵌套的上下文模式来支持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已保存且没问题。帖子永远不会保存,我收到错误:

CoreData:错误:将托管对象0x17dadd80(0x17daf930)从其上下文中删除后进行变更。

我认为,问题在于保存逻辑的背景。

2 个答案:

答案 0 :(得分: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更新上下文。

我希望这会有所帮助。

答案 1 :(得分:0)

Bart Jacobs的一篇很棒的教程题为:Core Data from Scratch: Concurrency详细描述了两种方法,更优雅的解决方案涉及父/子管理对象上下文,包括如何正确保存上下文。