3

我在 CoreData 中遇到了一些奇怪的行为,导致我的一个 MOC 最终处于不一致的状态。我已经在一个小示例项目中复制了这个问题

这是我的情况的基本概述:

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

  • 在我的示例项目中:

    • 我正在创建 1 个管道和 3 个示例框,它们都指向该管道。
    • 此示例数据是在使用以下命令创建的 MOC 上创建的NSMainQueueConcurrencyType
    • 我创建了一个后台 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];
}

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

更新:

管道名称:pipeline1 管道关系中的盒子:box3 box1 box2

存在的所有框实体:box3 box2 box1

管道名称:pipeline1 管道关系中的盒子:box3 box1 box2

存在的所有框实体:box3 box2

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

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

4

1 回答 1

1

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

细化:
上下文不处于不一致的状态。
为什么?

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

实际情况是:
1. 您在 MOC1(主上下文)中创建项目,然后使用 2 阶段获取请求记录对象。
1.1。从 (0) 开始,如果底层存储在两次提取之间发生变化,则每个提取请求都可能返回不同的数据集
。您创建的 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 执行第二次 fetch 请求(很可能,在 MOC2 保存到商店之后) 3.2.1. 你得到 box2,box3 4. 主运行循环结束和 MOC2现在可以将其更改合并到 MOC1

希望这能澄清一点。

在合并对 MOC1 的更改后尝试记录,看看事情是否应该如此。

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

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

于 2013-05-08T05:18:00.797 回答