Pencildrummer
Pencildrummer

Reputation: 91

Parent MOC get changes with empty data from child MOC

I'm stuck on this problem with CoreData and Parent-Child MOCs: when adding objects to child MOC, saving them and saving the parent MOC all the objects gets their attributes reset to defaultValue.

I pasted here the logs from the two MOCs, specifically are the "stringAttribute" and "date" attributes that in these log are reset.

I searched for this problem everywhere but i didn't find anything, I also looked at lots of implementation of Parent-Child MOCs but I can't figure out what I'm doing wrong.

Thanks in advance!

Here's the code snippets:

I add some NSManagedObject to the main context and then save with saveContext: method

// Another singleton method
- (void)anotherMethod
{
   [...]
   [self.managedObjectContext insertObject:managedObject];
   NSError *error;
   save = [self saveContext:&error];
   [...]
}

// Database manager singleton method

- (BOOL)saveContext:(DKError *__autoreleasing *)error
{

    __block BOOL save = NO;
    __block NSError *internalError;

    [self.managedObjectContext performBlockAndWait:^{
        internalError = nil;

        [self.managedObjectContext log]; // See log 1.1 below

        save = [self.managedObjectContext save:&internalError];
        if (!save) {
            NSLog(@"Error saving data in main context");
        } else {

            [self.managedObjectContext.parentContext performBlock:^{
                internalError = nil;
                save = NO;

                [self.managedObjectContext.parentContext log]; // See log 1.2 below

                save = [self.managedObjectContext.parentContext save:&internalError];
                if (!save) {
                    NSLog(@"Error saving data to disk!");
                }
            }];

        }
    }];
    *error = [DKError errorWithNSError:internalError]; // Custom error class
    return save;
}

Parent - Child contexts code

- (NSManagedObjectContext *)writerObjectContext
{
    if (_writerObjectContext != nil)
        return _writerObjectContext;

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _writerObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_writerObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _writerObjectContext;
}

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_managedObjectContext setParentContext:[self writerObjectContext]];

    return _managedObjectContext;
}

Log 1.1

Inserted objects:
{(
    <Entity: 0x9595120> (entity: Entity; id: 0x9582d40 <x-coredata:///Entity/t24D0F98B-CB94-41D3-BEDD-79913454A9152> ; data: {
    [...]
    dateAttribute = "2013-07-12 10:36:31 +0000";
    stringAttribute = ricercaEntity;
    [...]
})
)}

Log 1.2

Inserted objects:
{(
    <Entity: 0xb53ce80> (entity: Entity; id: 0x9582d40 <x-coredata:///Entity/t24D0F98B-CB94-41D3-BEDD-79913454A9152> ; data: {
    [...]
    dateAttribute = "2013-01-05 11:00:00 +0000";
    stringAttribute = nil;
    [...]
})
)}

UPDATE

I've should have mention that the managedObject added to the context is initialized with context nil. Then before calling saveContext: I check for existence of object.managedObjectContext and if it's nil then I'll set that as [self managedObjectContext], the one created with the method above. So either if the managedObject is created with nil context, or created with:

+ (id)newObjectForInsertion
{
    return [[self alloc] initWithEntity:[self entityDescription] insertIntoManagedObjectContext:[DKDatabaseManager defaultManager].managedObjectContext];
}

the associated managedObjectContext is on the same queue (NSMainQueueConcurrencyType).

Otherwise if the managedObject is create with +newObjectForInsertion all of the saveContext: concurrency-chain return YES and all the changes are passed to parent context.

I don't know if it's a bug or the way CoreData should work.

Same problem on Apple Developer Forums:

https://devforums.apple.com/thread/174677?tstart=90

Upvotes: 3

Views: 498

Answers (1)

Tala
Tala

Reputation: 8928

You should init context with concurrencyType:

context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

Also, set merge policy

[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];

NSMergeByPropertyObjectTrumpMergePolicy

This policy merges conflicts between the persistent store’s version of the object and the current in-memory version, giving priority to in-memory changes. The merge occurs by individual property. For properties that have been changed in both the external source and in memory, the in-memory changes trump the external ones.

Btw, I found similar question: strange-behavior-when-using-child-parent-nsmanagedobjectcontext look at the accepted answer which uses notifications to merge.

Upvotes: 1

Related Questions