核心数据变更没有合并

时间:2014-04-10 16:49:06

标签: ios multithreading core-data merge

我已经设置了一个非分层的双MOC架构(一个用于主线程,一个用于私有线程),带有用于合并更改的保存通知:

- (NSManagedObjectContext *)managedObjectContext
{
   if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        NSManagedObjectContext* mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [mainContext setPersistentStoreCoordinator:coordinator];
        [mainContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

        _managedObjectContext = mainContext;

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextDidSaveMainQueueContext:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:_managedObjectContext];
    }
    return _managedObjectContext;
}

- (NSManagedObjectContext *)privateManagedObjectContext
{
    if (_privateManagedObjectContext != nil) {
        return _privateManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {

        NSManagedObjectContext* privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [privateContext setPersistentStoreCoordinator:coordinator];
        [privateContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

        _privateManagedObjectContext = privateContext;

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextDidSavePrivateQueueContext:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:_privateManagedObjectContext];
    }
    return _privateManagedObjectContext;

}

- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
    @synchronized(self) {
        [self.managedObjectContext performBlock:^{
            [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
    @synchronized(self) {
        [self.privateManagedObjectContext performBlock:^{
           [self.privateManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];          
        }];
    }
}


现在,我在主MOC的主线程上更新了一些对象A,现在我在某个方法中并在我的私有MOC队列/线程的块内工作:

[self.privateManagedObjectContext performBlockAndWait:^{
   ...

我保存了我的主线程MOC:

[self.managedObjectContext performBlockAndWait:^{
    NSError *contextError;
    if (![self.managedObjectContext save:&contextError]) NSLog(@"ERROR SAVING MOC : %@ ; %@", contextError, [contextError userInfo]);
}];

保存成功(已验证)并触发执行合并的保存通知(已验证)。

但是MOC仍然不一致:当我从主MOC中获取对象A并记录其关系R中的对象数量时,我发现对象的数量与我从私有MOC和日志中获取对象A的时间不同关系R中的对象数量。

有点下游,在相关的事件链中,我去保存我的私人MOC。应用程序暂停(仅当我启用了所有例外或所有目标c异常断点时),并且可以恢复执行而不会有任何明显的伤害。此问题在此处描述:core data MOC save pauses execution and fails to save (w/o error or crash)。我有一种感觉它是相关的,但我怀疑这种合并失败是一个更基本的问题。

其他说明:

  • 我已尝试过每个合并政策
  • 我发现如果我尝试使用performBlock AndWait 执行合并,应用程序将无限期挂起;我不知道这是否是预期的行为。
  • 我已经阅读了我能找到的每一个问题,并尝试了我能想到的一切。

这段代码有问题吗?我还能尝试什么?谢谢!

2 个答案:

答案 0 :(得分:1)

您的合并代码是什么样的?如果在合并期间获得带有-performBlockAndWait的无限期块,则表示您在收到通知时已经在该上下文的线程上。不过,看看你的代码会有所帮助。

同时查看通知的观察者构造的代码会有所帮助。

在执行主线程和私有线程上下文时,为什么不在这里执行父/子构造?

至于初始化多个NSPersistentStoreCoordinator个实例的风险,这不是风险。即使您构建了100个PSC实例,它仍然可以正常对抗SQLite文件。 SQLite专为多用户访问而设计。我不明白这是怎么回事。

更新

好的,我不建议将更改从主要版本更改为私有版本。即使没有父/子设计,您的私人队列也应该被使用一次并被丢弃。两个方向的合并可能会很快变得混乱,我怀疑你是来回合并相同的数据。进行一些记录将确认。

我还建议使用父母/孩子。这种情况是它的设计目标,并将显着提高您的合并性能。这是一个相当小的代码更改(在私有中设置PSC,关闭观察者,很好去)来测试和验证性能变化。

答案 1 :(得分:1)

不是直接答案,而是......

查看THIS示例项目。

(希望所有断点都留在原地)

通过暂停和恢复线程,可能的输出可能是:

2014-04-11 20:50:16.199 RaceCondition[13787:60b] setting timestamp: 2014-04-11 17:50:16 +0000
2014-04-11 20:50:16.202 RaceCondition[13787:1303] thread 0x8f81f00
2014-04-11 20:50:16.202 RaceCondition[13787:60b] main context: <NSManagedObjectContext: 0x8d5c170> created
2014-04-11 20:50:16.202 RaceCondition[13787:1303] no private context found
2014-04-11 20:50:16.203 RaceCondition[13787:60b] main context: <NSManagedObjectContext: 0x8d5c170> already exists
2014-04-11 20:50:28.122 RaceCondition[13787:60b] thread 0x8e48cd0
2014-04-11 20:50:28.122 RaceCondition[13787:60b] no private context found
** 2014-04-11 20:50:46.866 RaceCondition[13787:1303] private context: <NSManagedObjectContext: 0x8c59b60> set
2014-04-11 20:50:46.867 RaceCondition[13787:1303] working with context: <NSManagedObjectContext: 0x8c59b60>
2014-04-11 20:50:46.868 RaceCondition[13787:1303] event timestamp in private context: 2014-04-11 17:50:16 +0000
** 2014-04-11 20:51:22.923 RaceCondition[13787:60b] private context: <NSManagedObjectContext: 0x8d5c6b0> set
2014-04-11 20:51:22.924 RaceCondition[13787:60b] setting new timestamp: 2014-04-11 17:52:16 +0000
2014-04-11 20:51:22.924 RaceCondition[13787:3503] thread 0x8d4d290
2014-04-11 20:51:30.123 RaceCondition[13787:60b] main context: <NSManagedObjectContext: 0x8d5c170> already exists
2014-04-11 20:51:22.924 RaceCondition[13787:3503] private context: <NSManagedObjectContext: 0x8d5c6b0> already exists
2014-04-11 20:51:30.123 RaceCondition[13787:3503] merging to private context: <NSManagedObjectContext: 0x8d5c6b0>
2014-04-11 20:51:30.124 RaceCondition[13787:60b] thread 0x8e48cd0
2014-04-11 20:51:30.124 RaceCondition[13787:3503] thread 0x8d4d290
2014-04-11 20:51:30.124 RaceCondition[13787:60b] private context: <NSManagedObjectContext: 0x8d5c6b0> already exists
2014-04-11 20:51:30.125 RaceCondition[13787:3503] private context: <NSManagedObjectContext: 0x8d5c6b0> already exists
2014-04-11 20:51:30.125 RaceCondition[13787:60b] main context: <NSManagedObjectContext: 0x8d5c170> already exists
2014-04-11 20:51:30.126 RaceCondition[13787:3503] thread 0x8d4d290
2014-04-11 20:51:30.126 RaceCondition[13787:3503] private context: <NSManagedObjectContext: 0x8d5c6b0> already exists
** 2014-04-11 20:51:30.126 RaceCondition[13787:3503] merging to private context: <NSManagedObjectContext: 0x8d5c6b0>
2014-04-11 20:51:30.127 RaceCondition[13787:3503] thread 0x8d4d290
2014-04-11 20:51:30.127 RaceCondition[13787:3503] private context: <NSManagedObjectContext: 0x8d5c6b0> already exists
2014-04-11 20:51:36.086 RaceCondition[13787:1303] event timestamp in private context: 2014-04-11 17:50:16 +0000

已加星标的行显示可能的竞争条件,导致BG操作不显示主要上下文所做出的更改。

修改
可以看出,创建了2个私有上下文:
  - 由BG操作使用
  - 由主线程的并行保存创建

当主上下文仅保存上下文时:正在合并更改。

由于设计的对称性,这可以用于重现主要上下文设置两次导致更新不传播到UI的情况。

多重协调员的风险是PO设计和实施中涉及的问题和竞争条件的另一个例子

如图所示,可能的原因可能是多个私人环境&#34;初始化导致错误的私人环境被初始化。