Временно управляваните обекти не са правилно обединени от дъщерен контекст към основен контекст

Имам многопоточно приложение, където трябва да обединя частен контекст с основния контекст, който от своя страна е свързан с контролера за постоянно съхранение.

Също така имам нужда да създавам временни обекти, които НЕ се управляват (докато по-късно реша да ги управлявам).

Първо се опитах да създам моите временни обекти, както следва;

NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:myMainQueueContext];
User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

След като реша да запазя обекта или не, аз просто;

[privateContext insertObject:user];

Преди да направя приложението многонишково, това работеше страхотно, но сега, след като леко разкъсах нещата и добавих многонишкова паралелност от дъщерен/родителски контекст, резултатът НЕ е според очакванията.

Като гледам „registeredObjects“ на контекста, мога да видя, че моят създаден и сега вмъкнат потребител се управлява в privateContext. След като го запазя, основният контекст се променя съответно и мога да видя, че има промени и че вече има един обект в регистрираните обекти.

Но като се вгледаме по-отблизо в ТОЗИ регистриран обект в основния контекст, разкриваме, че той е изпразнен. Без съдържание. Всички атрибути са нула или 0 в зависимост от типа. Следователно може да се очаква, че това може да се дължи на това, че objectId не е същият... но е ;( Това е същият обект. Но без съдържание.

Опитах се да получа някаква информация относно това безпокойство в друга публикация тук, но без успех.

Дъщерните контекстни обекти стават празни след сливане с родител/ основен контекст

Както и да е, най-накрая накарах нещата да работят, като промених начина, по който създавам обектите си;

User* user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:privateContext];

Изведнъж дъщерните ми обекти се обединяват с mainContext, без да губят съдържанието си, по неизвестни за мен причини, но за съжаление това също доведе до факта, че вече не мога да създавам временни неуправляеми обекти... ;( Четох, че Маркъс Зара подкрепи моя първи подход, когато става въпрос за създаване на неуправляеми обекти, но това не работи с обединяването на контексти в моето многонишково приложение...

Очаквам с нетърпение всякакви мисли и идеи -- аз ли съм единственият, който се опитва да създаде временни обекти в асинхронна работна нишка, където искам само да управлявам/обединявам подгрупа от тях до 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();
         }
    }];
}];

Горното НЕ работи, тъй като създава временен обект и след това го вмъква в контекста. Този лек мод обаче кара нещата да работят, но ми пречи да имам временни обекти...;

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.modelManager.mainManagedObjectContext];
    User* user = (User *)[[User alloc] initWithEntity:entity insertIntoManagedObjectContext:privateManagedObjectContext];

Затова се чудя; каква е разликата? Очевидно трябва да има някаква разлика, но не я разбирам.


person Markus Millfjord    schedule 17.02.2014    source източник


Отговори (1)


Доколкото мога да преценя, това е друг бъг в CoreData.
Мога да разбера донякъде „как“, но не и „защо“.

Както знаете, CoreData разчита в голяма степен на KVO. Управляваният контекст наблюдава промените в своите обекти като ястреб.
Тъй като вашите „Временни“ обекти нямат контекст, контекстът не може да проследи техните промени, докато не бъдат прикачени към него, така че не отчита правилно промените в родителския контекст (или изобщо). И така, родителският контекст ще получи „комитираната стойност“ на вмъкнатия обект, която се превръща в nil веднага щом вмъкнете обекта си в контекста с помощта на insertObject: (това е грешката, предполагам).

Така че измислих хитър план :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)

person Dan Shelly    schedule 23.02.2014
comment
По принцип исках да запазя сегашното си разделение на проблемите, при което бекенд комуникаторът не трябва да се притеснява какво се управлява или не, а вместо това просто връща набор от анализирани обекти (JSON -› Обекти). Кодът вече беше написан и бях щастлив, докато не разбрах, че .. не е безопасен за нишки и трябваше да добавя частни контексти, за да се слея с контекст на главна опашка. Добре, предполагам, но както и да е - това е причината. Сега пренаписах кода си, за да направя както предложихте; винаги добавяйте и премахвайте по-късно, ако не е необходимо. - person Markus Millfjord; 23.02.2014
comment
Но освен защо в моя коментар по-горе, оценявам вашето обяснение. Има смисъл! Очевидно това е KVO и аз проверих това, като зададох параметри СЛЕД insertObject, които според теорията остават след сливане с основния контекст. Харесвам вашата корекция и тя наистина върши работа, въпреки че вече не използвам неуправляемия обектен подход. Но със сигурност помага да разберем защо нещата тръгнаха на юг... ;) - person Markus Millfjord; 23.02.2014
comment
Що се отнася до отношенията, така или иначе нямах нужда от това, тъй като моята работна нишка за backend-comm анализира само JSON в неуправляеми обекти с параметри. Бяха установени връзки с други вече управлявани обекти на по-късен етап IFF Реших да запазя обекта (и insertObject). Независимо от това, харесвам новия подход за винаги вмъкване в контекста, тъй като бях прекарал доста часове в писане на мои собствени методи за добавяне, където неуправляваните временни обекти бяха вложени в съществуващия основен контекст. Ненужен код, който сега е бракуван. - person Markus Millfjord; 24.02.2014
comment
Радвам се, че помогнах. CoreData (предполагам) не е предназначен да работи по неприкрепен начин (поради зависимостта от KVO). weeding е метод, препоръчан от Apple (като част от оптимизациите на подхода „намери или създай“). - person Dan Shelly; 24.02.2014