核心数据更改不合并

Posted

技术标签:

【中文标题】核心数据更改不合并【英文标题】:core data changes don't merge 【发布时间】:2014-04-10 16:49:06 【问题描述】:

我已经建立了一个非分层的双 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)。我感觉它是相关的,但我怀疑无法正确合并是一个更根本的问题。

其他说明:

我已经尝试了所有合并策略 我发现如果我尝试使用 performBlockAndWait 执行合并,应用程序会无限期挂起;我不知道这是否是预期的行为。 我已经阅读了我能找到的所有关于此的问题,并尝试了我能想到的一切。

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

【问题讨论】:

您的实现中可能存在竞争条件。如果privateManagedObjectContext 从多个线程并行调用(主MOC 也是如此)。此外,不需要您的“@synchronized(self)”。确保主 MOC 和私有 MOC 没有被初始化两次 @DanShelly 感谢您的提示。我删除了@synchronized。我设置了一个断点并验证 MOC 只初始化了一次。如果您认为比赛条件可能会在这里发挥作用,我很乐意进行一些详细说明,但对我来说似乎不太可能,因为我正在以一种合理隔离的方式进行测试。在我正在排除故障的方法执行期间,不应有其他方法/线程访问核心数据,并且该方法始终使用 performBlockAndWait,所有获取请求也是如此。 考虑这种情况:您的视图控制器请求主上下文,并且正在执行 BG 操作,请求私有上下文。在初始化期间,他们都需要一个协调器,假设您实现了延迟加载,persistentStoreCoordinator 是从 2 个不同的线程并行调用的。这将导致每个上下文使用不同的协调器,或者如果上下文没有强烈地保持协调器,甚至可能使您的应用程序崩溃。这可能不会直接导致您的合并问题,但您可能需要考虑这一点。 拥有多个 NSPersistentStoreCoordinator 实例不会导致合并问题或 OP 描述的问题。 SQLite 和 Core Data 专为 NSPersistentStoreCoordinator 的多个实例并行运行而设计。 @MarcusS.Zarra 多个协调员可能不会导致所描述的问题,但是多个“私有上下文”会(合并直接到当前分配的“私有上下文”)。正如我所提到的,这只是使用 OP 给出的代码不安全的事情之一,但不一定会导致问题。 【参考方案1】:

您的合并代码是什么样的?如果您在合并期间收到带有-performBlockAndWait 的无限块,则这意味着您在收到通知时已经在该上下文的线程中。不过,这将有助于查看您的代码。

另外,查看通知的观察者构造代码也会有所帮助。

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

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

更新

好的,我不建议将更改从主要合并回私有。即使没有父/子设计,您的私有队列也应该使用一次并丢弃。双向合并可能会很快变得混乱,我怀疑您正在来回合并相同的数据。输入一些日志记录将确认这一点。

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

【讨论】:

我相信您请求的所有代码都显示在我帖子的大块中。我不反对使用父/子结构;我不这样做的唯一原因是有人指导我说这对他不起作用,并且他发现此通知/合并设置最实用。 好吧……我切换了,一开始它仍然无法正常工作,但当我使用嵌套的performBlocks 用于 2 个 MOC 时,我最终发现它被锁定了。我确信这是有充分理由的。更正后,它似乎可以很好地传播更改。再次感谢。【参考方案2】:

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

查看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 设计和实施中涉及的问题和竞争条件的另一个示例

如图所示,一个可能的原因可能是多个“私有上下文”初始化导致错误的私有上下文被初始化。

【讨论】:

感谢您的意见;我会对此保持警惕。

以上是关于核心数据更改不合并的主要内容,如果未能解决你的问题,请参考以下文章

核心数据锁因为合并?

在实体框架核心中合并迁移

我怎样才能明智地处理版本控制和核心数据模型?

合并 iCloud 和核心数据数据库

在扩展/合并核心数据模型时保留数据

核心数据与用于核心数据的单个 MOC 和主线程合并冲突