临时托管对象未正确从子上下文合并到主上下文
Posted
技术标签:
【中文标题】临时托管对象未正确从子上下文合并到主上下文【英文标题】:Temporary managed objects are not properly merged from child context to main context 【发布时间】:2014-02-17 12:16:53 【问题描述】:我有一个多线程应用程序,我需要将私有上下文合并到主上下文,而主上下文又连接到持久存储控制器。
我还需要创建不受管理的临时对象(直到我后来决定管理它们)。
首先,我尝试如下创建我的临时对象;
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:myMainQueueContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
在决定是否保留该对象后,我就干脆了;
[privateContext insertObject:user];
在我将应用程序设为多线程之前,这很好用,但现在在稍微拆开东西并通过子/父上下文添加多线程并发之后,结果并不如预期。
通过查看上下文的“registeredObjects”,我可以看到我创建的并且现在插入的用户是在 privateContext 中管理的。保存后,mainContext 发生了相应的变化,我可以看到它 hasChanges 并且在 registeredObjects 中现在有一个对象。
但仔细观察 mainContext 中的注册对象,发现它已被清空。没有内容。根据类型,所有属性都是 nil 或 0。因此,人们会认为这可能是因为 objectId 不一样......但它是 ;( 它是同一个对象。但没有内容。
我试图在此处的另一篇文章中就这个问题获得一些意见,但没有成功。
Child context objects become empty after merge to parent/main context
不管怎样,我终于通过改变我创建对象的方式来完成工作;
User* user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:privateContext];
突然间,我的子对象被合并到 mainContext 而不会丢失它们的内容,原因我不知道,但不幸的是,这也导致我不能再创建临时的非托管对象......;(我读到 Marcus在创建非托管对象时,Zarra 支持我的第一种方法,但这不适用于在我的多线程应用程序中合并上下文...
期待任何想法和想法——我是唯一一个尝试在异步工作线程中创建临时对象的人吗,我只想管理/合并其中的一部分到 mainContext?
编辑
具体代码显示什么是有效的,更重要的是什么是无效的;
//Creatre private context and lnk to main context..
NSManagedObjectContext* privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
//Link private context to main context...
privateManagedObjectContext.parentContext = self.modelManager.mainManagedObjectContext;
[privateManagedObjectContext performBlock:^()
//Create user
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.modelManager.mainManagedObjectContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
[user setGuid:@"123123"];
[user setFirstName:@"Markus"];
[user setLastName:@"Millfjord"];
[privateManagedObjectContext insertObject:user];
//Debug before we start to merge...
NSLog(@"Before private save; private context has changes: %d", [privateManagedObjectContext hasChanges]);
NSLog(@"Before private save; main context has changes: %d", [self.modelManager.mainManagedObjectContext hasChanges]);
for (NSManagedObject* object in [privateManagedObjectContext registeredObjects])
NSLog(@"Registered private context object; %@", object);
//Save private context!
NSError* error = nil;
if (![privateManagedObjectContext save:&error])
//Oppps
abort();
NSLog(@"After private save; private context has changes: %d", [privateManagedObjectContext hasChanges]);
NSLog(@"After private save; main context has changes: %d", [self.modelManager.mainManagedObjectContext hasChanges]);
for (NSManagedObject* object in [privateManagedObjectContext registeredObjects])
NSLog(@"Registered private context object; %@", object);
for (NSManagedObject* object in [self.modelManager.mainManagedObjectContext registeredObjects])
NSLog(@"Registered main context object; %@", object);
//Save main context!
[self.modelManager.mainManagedObjectContext performBlock:^()
//Save main context!
NSError* mainError = nil;
if (![self.modelManager.mainManagedObjectContext save:&mainError])
//Opps again
NSLog(@"WARN; Failed saving main context changes: %@", mainError.description);
abort();
];
];
上述方法不起作用,因为它创建了一个临时对象,然后将其插入到上下文中。但是,这个轻微的 mod 可以让事情正常工作,但会阻止我拥有临时对象......;
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.modelManager.mainManagedObjectContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:privateManagedObjectContext];
因此,我想知道;有什么不同?很明显,肯定有一些区别,但我不明白。
【问题讨论】:
【参考方案1】:据我所知,这是另一个 CoreData 错误。 我可以理解它的“如何”,但不能理解它的“为什么”。
如您所知,CoreData 严重依赖 KVO。托管上下文像鹰一样观察其对象的变化。
由于您的“临时”对象没有上下文,因此在将它们附加到上下文之前,上下文无法跟踪它们的更改,因此它不会正确(或根本不会)报告对父上下文的更改。因此,一旦您使用insertObject:
将对象插入上下文,父上下文将获得插入对象的“提交值”,该值变为nil
(这是我猜的错误)。
所以我制定了一个狡猾的计划:D 我们将摆脱困境!
介绍 NSManagedObjectContext+fix.m:
//Tested only for simple use-cases (no relationship tested)
+ (void) load
Method original = class_getInstanceMethod(self, @selector(insertObject:));
Method swizzled = class_getInstanceMethod(self, @selector(__insertObject__fix:));
method_exchangeImplementations(original, swizzled);
- (void) __insertObject__fix:(NSManagedObject*)object
if (self.parentContext && object.managedObjectContext == nil)
NSDictionary* propsByName = [object.entity propertiesByName];
NSArray* properties = [propsByName allKeys];
NSDictionary* d = [object committedValuesForKeys:properties];
[propsByName enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSPropertyDescription* prop, BOOL *stop)
if ([prop isKindOfClass:[NSAttributeDescription class]])
[object setValue:[(NSAttributeDescription*)prop defaultValue] forKey:key];
else if ([prop isKindOfClass:[NSRelationshipDescription class]])
[object setValue:nil forKey:key];
];
[self __insertObject__fix:object];
[object setValuesForKeysWithDictionary:d];
else
[self __insertObject__fix:object];
这可能会帮助您使您的代码更加简洁。
但是,我可能会尽量避免这种类型的插入。 我真的不明白您需要将对象插入特定上下文并让它挂起,直到您决定是否需要它。
总是将你的对象插入到上下文中会不会更容易(如果需要长时间将值保存在字典中)。但是当您决定对象不应该“上架”时,只需将其删除?
(这叫除草)
【讨论】:
基本上,我想保持我当前的“关注点分离”,后端通信器不需要关心管理的内容,而是简单地返回一组解析的对象(JSON - >对象)。代码已经写好了,我很高兴,直到我意识到......它不是线程安全的,我需要添加私有上下文以合并到主队列上下文。好吧,我想,但无论如何——这就是原因。我现在已经按照您的建议重写了我的代码;始终添加,如果不需要,稍后删除。 但是除了我上面评论中的“为什么”之外,我感谢您的解释。这说得通!显然,它是 KVO,我通过在 insertObject 之后设置参数来仔细检查这一点,根据理论,在合并到主上下文后会坚持。我喜欢你的修复,它确实可以解决问题,即使我不再使用非托管对象方法。但它肯定有助于了解为什么事情会向南......;) 至于关系,无论如何我都不需要,因为我的后端通信工作线程仅将 JSON 解析为带有参数的非托管对象。在后期 IFF 我决定保留该对象(和 insertObject)时,建立了与其他“已管理”对象的关系。无论如何,我喜欢新的 always 插入上下文方法,因为我花了很多时间编写自己的“添加”方法,其中非托管临时对象嵌套到现有主上下文中。现在废弃的不必要代码。 很高兴能帮上忙。 CoreData(我猜)不打算以独立的方式工作(由于 KVO 依赖)。除草是 Apple 推荐的一种方法(作为查找或创建方法优化的一部分)。以上是关于临时托管对象未正确从子上下文合并到主上下文的主要内容,如果未能解决你的问题,请参考以下文章
合并到主线程上下文时,在后台线程上下文中更新的可转换属性未保存