How to merge changes from one child managed object context to another via a parent MOC?
Asked Answered
L

1

10

Hello (actual question at the bottom).

In iOS 5 there is the introduction of parent-child managed object contexts in CoreData.

I have a standard NSFetchedResultsController and UITableVeiwController working together to fetch a master list from the store. The fetched results controller's managed object context is a child with a parent context:

// AppDelegate.m

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

    __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

    // primary managed object context has NSPrivateQueueConcurrencyType
    [__managedObjectContext setParentContext:[self primaryObjectContext]];
    return __managedObjectContext;
}

The table view controller presents a modal view controller to add a new record, but uses a separate managed object context to do so (this context is another child of the parent context). This context gets saved in a delegate callback in the table view controller:

- (void)addGame
{
    // New child context

    [self setBuildManagedObectContext:[[NSManagedObjectContext alloc] init]];
    [[self buildManagedObectContext] setParentContext:[[[self team] managedObjectContext] parentContext]];

    Team *buildTeam = (Team *)[[self buildManagedObectContext] objectWithID:[[self team] objectID]];
    Game *buildGame = [NSEntityDescription insertNewObjectForEntityForName:@"Game" 
                                                inManagedObjectContext:[self buildManagedObectContext]];


    [buildGame setTeam:buildTeam];

    BuildViewController *buildVC = [[BuildViewController alloc] initWithGame:buildGame delegate:self];
    UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:buildVC];
    [[self navigationController] presentViewController:navCon animated:YES completion:nil];
}

#pragma mark - Build view controller delegate

- (void)buildViewController:(BuildViewController *)controller didFinishWithSave:(BOOL)save
{
    if (save)
    {
        NSError *error = nil;
        if (![[self buildManagedObectContext] save:&error])
        {
            NSLog(@"Error saving new game");
            abort();
        }
    }
    [self setBuildManagedObectContext:nil];
    [[self navigationController] dismissViewControllerAnimated:YES completion:nil];
}

Question:

It was my understanding that with parent-child contexts, the parent context would get the save notification from its child, and then notify all its children. So in my example, the build context should trigger the parent context to tell the fetched results controller to merge the changes.

This isn't happening for me, the records are successfully created, but the fetched results controller isn't picking them up. I know it used to be the case you had to implement you own merge from the save notification (as seen in CoreDataBooks). But I thought the child-parent contexts solved this issue. I tried saving the child and then the parent, but didn't seem to make a difference. Can someone explain this to me please? (Note: These are not contexts on separate/background threads)

Many Thanks

Lightproof answered 20/3, 2012 at 17:1 Comment(0)
H
14

According to the WWDC 2011 presentation for "What's new in Core Data" it says you should save like this:

[child save:&error]; 
[parent performBlock:^{
    [parent save:&parentError];
}];

From my understanding this causes the parent to receive and merge in changes from the child context. One thing to note is that the parent and all children must be created with the same concurrency type.

-- Edit removed incorrect assumption that the parent pushes changes to the children, it does not.

Hanahanae answered 20/3, 2012 at 18:54 Comment(9)
Thanks, I am still having problems with the fetched results controller picking up the save on the parent. hmmm :/Lightproof
Are you implementing the 4 important FRC delegate methods correctly? If so you can try telling your FRC to execute it's fetch again, or destroy it and recreate it again. Creating and destroying FRCs is a relatively cheap operation. I dealt with a similar issue but in my scenario my predicate needed to change on occasion. I wound up destroying and recreating my FRCs and telling my tableview to reload its data.Hanahanae
Yep the delegate is just the standard methods. Still not working, even tearing down the frc. Will keep trying different things. Appreciate the help though.Lightproof
@Hanahanae "One thing to note is that the parent and all children must be created with the same concurrency type.". This has helped me in the same issue.Bemba
"One thing to note is that the parent and all children must be created with the same concurrency type." Hmm... If you want to perform a background fetch of data updating the MOC and support the UI in the main thread, they will not be the same concurrency type???Linear
@user786383 Using NSMainQueueConcurrencyType for your concurrency type doesn't adversely affect or block your UI thread. It is preferable to use that concurrency type if you are performing fetches with the MOC to update UI elements (for example filling a UITableView via an NSFetchedResultsController).Hanahanae
Parent contexts don't "push changes" to existing child contexts. So if you make a change on child context 1, save child context 1, and save the parent context, live instances of that entity in child context 2 will not have that change. You have to call reset on child context 2 and re-fetch the object to see the change made in child 1. This isn't an issue if child context 2 doesn't have any cached instances of the entity, but it's important to understand that it's because the context pulls from the persistent store when it fetches, not because a parent context pushes to its children.Amorist
@ChristopherPickslay You are a beautiful person.Radiolucent
@Hanahanae I think you're wrong on this one. A child MOC can have a different concurrency type than the parent: #14284801Lota

© 2022 - 2024 — McMap. All rights reserved.