NSManagedObject值是正确的,然后在将更改从父NSManagedObjectContext合并到子NSManagedObjectContext时不正确

我在使用2个NSManagedObjectContext ,在不同的线程上运行以及将更改从父级迁移到子级时遇到了Core Data问题。 从本质上讲,我能够将更改从父级转移到子级,但在执行此操作后,更改将立即丢​​失。

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

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

 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 ,它让它知道主线程上下文中的对象已被更改。 它调用保存主上下文的保存,它将更改保存到syncMOC。
  2. 当syncManager收到NSManagedObjectContextDidSaveNotification以指示保存已完成时,它会从同步上下文中收集新更改的对象,并将更改发送到服务器。 然后它对同步MOC进行保存,将数据发送到SQLite。 请注意,每个对象都有一个uuid字段,我将其创建为可移植ID – 不要与Core Data的objectID混淆,也不要与服务器提供的lastSynced时间戳混淆。
  3. 服务器响应发送的对象的更新时间戳,以及发生的任何其他更改。 在说明问题的最简单的情况下,收到的是一组记录,其中包含syncManager刚刚发送的对象的uuid和更新的lastSynced时间。
  4. 对于每次更新,syncManager都会更新syncContext中的对象,并将对象(而不是uuid)的NSManagedObject objectID存储在数组中。
  5. 然后,syncManager对同步MOC进行保存,将数据写入磁盘并发布消息,为主MOC提供更新对象的objectID数组。 此时,如果我对syncMOC中的所有实体进行提取并将其转储到日志中,则它们都具有正确的值。 此外,如果我查看磁盘上的SQLite数据库,它也具有正确的值。
  6. 这里是关于如何在主线程上合并更新的缩写代码(一些错误检查和非必要的东西),以及关于发生了什么的注释:(注意:我在代码中一直小心使用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语句后的注释中会出现问题。 该对象最初具有来自父上下文的正确值,但随后它们变得不正确。 具体来说,请查看时间戳。

有谁知道为什么会这样? 我应该注意到,最后进行提取的业务是调试的一部分。 我注意到在程序中保留的NSManagedObjects没有正确的值,即使我在上面的代码中看到事情已经正确更新,并且通过unquing,它们也应该更新。 我认为可能发生的事情是我正在使用正确的值创建“额外”对象,而旧的对象仍然存在。 然而,获取显示正确的对象和只有正确的对象,只有坏的值。

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

任何帮助深表感谢!

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

我在某些时候注意到,返回主上下文的对象ID具有不正确的核心数据ID – 它们应该是永久性的,但不是。 事实上,在合并期间,我意识到我的主MOC中给定对象的ID和该对象合并更改的ID都是临时的,但不同。 但它们本身不应该是永久性身份证。 针对该问题的Stack Overflow搜索引发了我的回答https://stackoverflow.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") } } 

修好了! 所以我最初观察到的是合并实际上并没有发生。 有两个对象存活,原本应该是一个。

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