临时托管对象未正确地从子上下文合并到主上下文

时间:2014-02-17 12:16:54

标签: multithreading core-data nsmanagedobject nsmanagedobjectcontext temporary-objects

我有一个多线程应用程序,我需要将私有上下文合并到主上下文,而主上下文又连接到持久存储控制器。

我还需要创建非托管的临时对象(直到我后来决定管理它们)。

首先,我尝试按如下方式创建临时对象;

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];

因此,我在想;有什么不同?显然,一定有一些区别,但我不明白。

1 个答案:

答案 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)