在后台上下文中mergeChangesFromContextDidSaveNotification之后处于不一致状态的NSManagedObjectContext

时间:2013-05-07 21:56:16

标签: ios core-data merge nsmanagedobjectcontext

我在CoreData中遇到一些奇怪的行为导致我的一个MOC最终处于不一致状态。我已经用小样本project

复制了这个问题

以下是我的情况的基本概述:

  • 我有两种实体类型,管道和盒子。每个管道可以包含0个或更多个框,每个框只是一个管道的一部分

  • 在我的示例项目中:

    • 我正在创建一个管道和3个样本框,所有这些都指向该管道。
    • 此示例数据是在使用NSMainQueueConcurrencyType
    • 创建的MOC上创建的
    • 我创建了一个后台MOC(`NSPrivateQueueConcurrencyType')并获取所有框并只删除其中一个。
    • 此删除导致管道更新(关系中应该少一个框)
    • 当我保存背景MOC时,我尝试将更改合并到主队列MOC

问题是在合并之后,主队列上下文成功合并了删除但没有将编辑合并到管道,这应该表明关系中只有一个框。

不知何故它没有合并整个变化。

以下是一些代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    // create a core database and a moc on the ui thread
    [self initCoreData];

    // fill up db with dummy data all on the main thread
    [self createDummyData:self.uiContext];

    [self printUIContextContents];

    // now create a background moc
    NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    // when the backgrouynd context saves, merge changees in to UI context
    [backgroundContext performBlockAndWait:^{
        backgroundContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
        [backgroundContext setPersistentStoreCoordinator:self.persistentCoordinator];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSave:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
    }];


    // now delete a box on background thread
    [backgroundContext performBlock:^{
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
        request.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"box1"];

        NSError* error = nil;
        NSArray* boxes = [backgroundContext executeFetchRequest:request error:nil];
        if (error != nil){
            NSLog(@"Error in deleting box: %@", error);
        }

        Box* box = (Box*)boxes[0];

        [backgroundContext deleteObject:box];
        [backgroundContext save:nil];
    }];

    [self printUIContextContents];


    return YES;
}

- (void) printUIContextContents {
    [self.uiContext performBlockAndWait:^{
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Pipeline"];
        NSArray* pipelines = [self.uiContext executeFetchRequest:request error:nil];
        for (Pipeline* pipeline in pipelines) {
            NSLog(@"Pipeline Name: %@", pipeline.name);
            NSLog(@"\tBoxes that are in the pipeline relationship: ");
            for (Box* box in pipeline.boxes) {
                NSLog(@"\t\t%@", box.name);
            }
        }

        NSLog(@" ");

        request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
        NSArray* boxes = [self.uiContext executeFetchRequest:request error:nil];
        NSLog(@"All Boxes Entities Present:");
        for (Box* box in boxes) {
            NSLog(@"\t%@", box.name);
        }
        NSLog(@" ");
        NSLog(@" ");
    }];
}

- (void)mocDidSave:(NSNotification *)notif {

    [self.uiContext performBlockAndWait:^(void) {
        [self.uiContext mergeChangesFromContextDidSaveNotification:notif];
    }];
}


- (void) createDummyData:(NSManagedObjectContext*)context {
    NSArray* boxNames = [NSArray arrayWithObjects:@"box1", @"box2", @"box3", nil];
    NSString* pipelineName = @"pipeline1";

    [self.uiContext performBlockAndWait:^{
        Pipeline* pipe = (Pipeline*)[self createEntity:@"Pipeline" inContext:self.uiContext];
        pipe.name = pipelineName;

        for (NSString* boxName in boxNames) {
            Box* box = (Box*)[self createEntity:@"Box" inContext:self.uiContext];
            box.name = boxName;
            box.pipeline = pipe;
        }

        NSError* error = nil;
        [self.uiContext save:&error];
        if (error != nil){
            NSLog(@"Error in create dummy data: %@", error);
        }
    }];
}


- (void) initCoreData {
    NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
    NSURL* storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"StreakDB.sqlite"];
    [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];

    NSManagedObjectModel* objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSError *error = nil;
    self.persistentCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
    if (![self.persistentCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         SOME ERROR HANDLING HERE
         */
    }

    self.uiContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [self.uiContext setPersistentStoreCoordinator:self.persistentCoordinator];
    self.uiContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
}

- (NSManagedObject*)createEntity:(NSString *)entityType inContext:(NSManagedObjectContext *)context {
    return [NSEntityDescription insertNewObjectForEntityForName:entityType
                                         inManagedObjectContext:context];
}

任何想法可能会发生什么?这里是一个示例项目,用于说明问题(并打印出结果):project

更新

管道名称:pipeline1     处于管道关系中的框:         BOX3         BOX1         BOX2

所有盒子实体存在:     BOX3     BOX2     BOX1

管道名称:pipeline1     处于管道关系中的框:         BOX3         BOX1         BOX2

所有盒子实体存在:     BOX3     BOX2

正如您所看到的,当我第二次打印出uiContext时,它处于不一致状态。具体来说,上下文中有2个框,但是管道的关系指向3个框 - 因此不一致。

据我所知,后台保存可能在第二次打印输出之前或之后完成,但在任何一种情况下,上下文的状态应该是否一致? (即3个盒子,关系中的3个项目或2个盒子和2个关系中的项目)。

1 个答案:

答案 0 :(得分:1)

这可能是线程问题(您正在并行执行BG操作)。尝试将其更改为:[backgroundContext performBlockAndWait: ...];

<强>精化:
上下文不是一个不一致的状态 为什么呢?

0:每次获取请求都是对商店的访问,并且不依赖于上下文在执行时包含的数据,所检索的对象与上下文的现有行缓存相匹配,以便重复使用现有项目信息并防止上下文中的项目重复,您可以获得当前商店状态的快照。

实际发生的是:
1.在MOC1(主上下文)中创建项目,然后使用2阶段提取请求记录对象 1.1。从(0)开始,如果底层存储在读取之间发生变化,则每个获取请求可以返回不同的数据集 2.您创建使用另一个线程执行其操作的MOC2,但它的创建阻止了MOC1的线程(主线程)。
2.1。您使用MOC2线程执行异步块 2.2。 MOC2正在删除一些对象
2.3。 MOC2保存其更改(商店在此处更改,合并前)
2.4。 MOC2正在尝试将更改合并到MOC1,但不能因为MOC1的线程忙于执行您的日志功能(参见(3.))。
第3。注意主线程现在再次进入你的日志函数并且当前的运行循环还没有退出(在2.1。操作的并行中),所以合并到MOC1将不可能在主循环完成其循环。
3.1。 MOC1执行日志功能第一次获取请求(很可能,在MOC2进行更改以将其更改保存到存储之前,此请求将阻止协调器,以便即使MOC2准备好保存协调器也将被阻塞,直到第一次获取为止完成。
3.1.1。你得到box1,box2,box3 3.2。 MOC1执行第二次获取请求(很可能,在MOC2保存到商店之后) 3.2.1。你得到box2,box3 4.主runloop结束和MOC2现在可以将其更改合并到MOC1

希望这有点澄清。

将更改合并到MOC1后尝试记录,并看到事情应该如此。

在处理多个线程时,您需要通过使用通知(如您所做)或使用父子架构来同步您的MOC。

在父子架构中,这不会发生,但实际写入商店的上下文将是主要上下文,这将阻止您的主线程。