【问题标题】:NSManagedObject values are correct, then incorrect when merging changes from parent to child NSManagedObjectContextNSManagedObject 值是正确的,然后在将更改从父合并到子 NSManagedObjectContext 时不正确
【发布时间】:2026-02-07 08:35:01
【问题描述】:

我在使用 2 NSManagedObjectContext、在不同线程上运行以及将更改从父级迁移到子级时遇到了核心数据问题。从本质上讲,我可以将更改从父级拉到子级,但是这样做之后,更改就会丢失。

我正在构建的应用程序是用于在多个设备和服务器之间同步模型的测试。

保存用户与之交互的对象的上下文位于主线程上,并被配置为同步上下文的子级,并像这样创建(省略错误检查)

NSManagedObjectContext *parentMOC = self.syncManagedObjectContext;
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

[_managedObjectContext performBlockAndWait:^() {
    [_managedObjectContext setParentContext:parentMOC];            
}];

syncManagedObjectContext 是父上下文,是 syncManager 与服务器执行同步的地方。它收集用户修改的对象,将更改发送到服务器并合并收到的更改。 syncManagedObjectContext 还将其数据发送到PersistentStoreCoordinator 以存储在SQLite 中。上下文在后台“线程”上运行,因此同步和存储不会阻塞主线程。以下是它的创建方式:

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
_syncManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_syncManagedObjectContext performBlockAndWait:^(){
    [_syncManagedObjectContext setPersistentStoreCoordinator:coordinator];
}];

同步逻辑流程

当 syncManager 从主上下文处理 NSManagedObjectContextObjectsDidChangeNotification 时,同步开始。以下是所发生情况的大致流程:

  1. syncManager 处理NSManagedObjectContextObjectsDidChangeNotification,这让它知道在主线程上下文中对象已被更改。它在主上下文中调用 save 来保存对 syncMOC 的更改。
  2. 当syncManager 收到NSManagedObjectContextDidSaveNotification 表示保存已完成时,它会从同步上下文中收集新更改的对象并将更改发送到服务器。然后它会保存将数据发送到 SQLite 的同步 MOC。请注意,每个对象都有一个 uuid 字段,我将其创建为可移植的 id - 不要与 Core Data 的 objectID 以及服务器提供的 lastSynced 时间戳混淆。
  3. 服务器以已发送对象的更新时间戳以及已发生的任何其他更改作为响应。在说明问题的最简单情况下,收到的是一组记录,其中包含 uuid 和 syncManager 刚刚发送的对象的更新 lastSynced 时间。
  4. 对于每次更新,syncManager 都会更新 syncContext 中的对象并将对象的 NSManagedObject objectID(不是 uuid)存储在一个数组中。
  5. 然后,syncManager 在同步 MOC 上进行保存以将数据写入磁盘并发布一条消息,为主 MOC 提供更新对象的 objectID 数组。此时,如果我对 syncMOC 中的所有实体进行提取并将它们转储到日志中,它们都具有正确的值。此外,如果我查看磁盘上的 SQLite 数据库,它也有正确的值。
  6. 这里是如何在主线程中合并更新的缩写代码(一些错误检查和非必要的东西被删除),以及关于发生了什么的 cmets:(注意:我在代码中小心使用 performBlock从调试器中的跟踪来看,一切都发生在正确的线程上。)
-(void)syncUpdatedObjects: (NSNotification *)notification
 {
    NSDictionary *userInfo = notification.userInfo;
    NSArray *updates = [userInfo objectForKey:@"updates"];

    NSManagedObjectContext *ctx = self.managedObjectContext;

    [ctx performBlock:^() {
        NSError *error = nil;

        for (NSManagedObjectID *objID in updates) {
            NSManagedObject *o = [ctx existingObjectWithID:objID error:&error];
            // if I print out o or inspect in the debugger, it has the correct, updated values.  


            if (o) {
                [ctx refreshObject:o mergeChanges:YES];
                // refreshObject causes o to be replaced by a fault, though the NSLog statement will pull it back.
                // NOTE: I’ve used mergeChanges:NO and it doesn’t matter

                NSLog(@"uuid=%@, lastSynced = %@", [o valueForKey:@"uuid”], [o valueForKey:@"lastSynced"]);
                // Output: uuid=B689F28F-60DA-4E78-9841-1B932204C882, lastSynced = 2014-01-15 05:36:21 +0000
                // This is correct. The object has been updated with the lastSynced value from the server.               

           }

        }
        NSFetchRequest *request = [[NSFetchRequest alloc] init];

        NSEntityDescription *entity = [NSEntityDescription entityForName:@“MyItem"
                                                  inManagedObjectContext:ctx];
        request.entity = entity;
        NSArray *results = [ctx executeFetchRequest:request error:&error];
        for (MyItem *item in results)
            NSLog(@"Item uuid %@ lastSynced %@ ", item.uuid, item.lastSynced);
        // Output:  uuid B689F28F-60DA-4E78-9841-1B932204C882 lastSynced 1970-01-01 00:00:00 +0000
        // Now the objects have incorrect values!
    }];

 }

如果您错过了它,问题就出现在 NSLog 语句之后的 cmets 中。该对象最初具有来自父上下文的正确值,但随后它们变得不正确。具体看时间戳。

有人知道为什么会发生这种情况吗?我应该注意到,最后进行 fetch 的业务是调试的一部分。我注意到程序中保留的 NSManagedObjects 没有正确的值,即使我看到上面的代码中的内容已正确更新,并且通过唯一性,它们也应该更新。我认为可能发生的事情是我正在创建具有正确值的“额外”对象,而旧对象仍然存在。但是,提取显示正确的对象,并且只有正确的对象在周围,只有错误的值。

还有一件事,如果我在此函数运行后在父上下文中执行相同的 fetch,它会显示正确的值,就像 SQLite 一样。

非常感谢任何帮助!

【问题讨论】:

    标签: ios cocoa core-data nsmanagedobject nsmanagedobjectcontext


    【解决方案1】:

    我终于找到了这个问题的答案,希望它可以帮助其他人。

    我注意到返回到主上下文的对象 ID 不正确 Core Data ID - 他们应该是永久的,但不是。事实上,在合并期间,我 意识到我的主 MOC 中给定对象的 ID 和要合并的更改的 ID 该对象都是临时的,但不同。但也不是他们应该拥有的永久身份证。 在 Stack Overflow 上搜索该问题导致我 这个答案https://*.com/a/11996957/1892468 提供了一个已知核心的解决方法 数据错误

    所以问题不在于我做错了,而在于 Core Data 没有做它应该做的事情 说可以。解决方法是在主对象上下文的保存操作期间,我添加了以下内容 调用 save 之前的代码。

    if ctx.insertedObjects.count > 0 {
        do {
            try ctx.obtainPermanentIDsForObjects(Array(ctx.insertedObjects))
        } catch {
            log.error("Unable toobtain permanent ids for inserts")
        }
    }
    

    这解决了它!所以我最初观察到的是合并实际上并没有采取 地方。本来应该是 1 个的,但有 2 个对象还活着。

    【讨论】:

      【解决方案2】:

      您可以简单地订阅同步上下文的 NSManagedObjectContextDidSaveNotification,然后通过调用 -mergeChangesFromContextDidSaveNotification: 将更改合并到 UI 上下文中。

      【讨论】:

      • 我认为这是我应该尝试的。我不喜欢这样的原因是,在同步上下文中所做的大部分更改都是同步的内务处理,对孩子来说并不重要。例如,在同步时,我在每个对象上设置一个标志以指示需要同步,当我收到来自服务器的默认回复时,我设置 lastSynced 时间并清除该标志。如果可以避免的话,我不打算将这些改变推给孩子。应用程序数据和关系的更改是重要的事情。但是,由于我的“方案”不起作用,我会尝试的!还是很好奇出了什么问题。
      • 有趣 - 所以我改成了mergeChangesFromContextDidSaveNotification,并且在模拟器中运行时日期正确地出现了。在我的 iPod 上运行,它们仍然不能正确合并。
      • 您是从要合并的上下文块中调用它吗?
      • 是的,我是从上下文中调用它的。为了让事情顺利进行,这需要花费很多时间。当我在调试器中跟踪同步时,一切似乎都在正确的线程上。如果可以调用一些 Core Data API 来检查一致性和其他状态,那就太好了。感谢您的意见,阿列克谢!