我有一个多线程应用程序,我需要将私有上下文合并到主上下文,而主上下文又连接到持久存储控制器。
我还需要创建非托管的临时对象(直到我后来决定管理它们)。
首先,我尝试按如下方式创建临时对象;
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中的那个registeredObject,就会发现它已经被清空了。没有内容。所有属性均为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];
因此,我在想;有什么不同?显然,一定有一些区别,但我不明白。
答案 0 :(得分:2)
据我所知,这是另一个CoreData错误
我可以在某种程度上理解“如何”,但不能理解它的“原因”。
如您所知,CoreData严重依赖KVO。托管上下文会像鹰一样观察其对象的变化
由于您的“临时”对象没有上下文,因此上下文在附加到上下文之前无法跟踪它们的更改,因此它不会正确地(或根本)报告对父上下文的更改。因此,父上下文将获得插入对象的“提交值”,一旦使用nil
将对象插入上下文,该对象将变为insertObject:
(这是我猜的错误)。 BR />
所以我设计了一个狡猾的计划: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];
}
}
这可能会帮助您保持代码更加真实。
但是,我可能会尽量避免这种类型的插入
我真的不明白你需要将一个对象插入一个特定的上下文并让它悬空,直到你决定是否需要它为止。
始终将对象插入上下文(如果需要延长一段时间,请将值保留在字典中)会不会更容易。但是当你决定对象不应该“点击商店”时,只需将其删除即可?
(这称为除草BTW)