Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSManagedObjectContext - how to update child when parent changes?

The Apple documentation is not clear (or I cannt find it) on what happens in the case of a parent and child MOC when the parentMOC has inserts followed by saves.

I'm using MARCUS ZARRA's http://martiancraft.com/blog/2015/03/core-data-stack/ method, with a privateQMOC at the top and the childMainMOC as the main thread one.

Problem

I add 10,000 objects to the privateMOC via a background internet request(s) calling save on the privateMOC, but any NSFetchedResultsControllers built on the childMainMOC context never call my delegate after the parent saves. So the interface does not update to show changes in the parentMOC.

I would like to call something that will update all the objects in the childMainMOC - which should then call the delegate methods on the child controller.

Or some other solution.

like image 950
Tom Andersen Avatar asked Oct 19 '22 12:10

Tom Andersen


2 Answers

Ok - so I figured it all out.

The model is as in: Marcus Zarra's http://martiancraft.com/blog/2015/03/core-data-stack/ The privateMOC is at the root, this allows for better performance and is also needed that way for other reasons. I use only 2 MOCs: the private root one and the child one which is a main thread one.

Then read this: Basically he explains how Core Data handles notifications, etc. http://benedictcohen.co.uk/blog/archives/308

THEN - the final thing that I needed to do: make sure all objectIDs in the program are real permanent ones.

- (id)insertNewObjectForEntityName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context {

NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];

// Make sure all inserted objects have a permanent ID.
// THIS IS VITAL. Without getting the permanentIDs, changes that come in from the web will not propogate to the child mainQ MOC and the UI will not update.
// Tested to be performant.
//NSLog(@"testing this - object.objectID is a temp now I think: %@ isTemp:%d", object.objectID, (int) [object.objectID isTemporaryID]);
// http://stackoverflow.com/questions/11990279/core-data-do-child-contexts-ever-get-permanent-objectids-for-newly-inserted-obj
NSError* error = nil;
[context obtainPermanentIDsForObjects:@[object] error:&error];
if (error || [object.objectID isTemporaryID])
    NSLog(@"obtainPermanentIDsForObjects is NOT WORkING - FIX: a new %@ isTemp: %d !!", entityName, (int) [object.objectID isTemporaryID]);

return object;

}

Also I needed to as per Benedicts article - listen for changes in the parent root private MOC

/// . http://benedictcohen.co.uk/blog/archives/308  good info !
/// I need this firing as sometimes objects change and the save notification below is not enough to make sure the UI updates.
- (void)privateQueueObjectContextDidChangeNotification:(NSNotification *)notification {
    NSManagedObjectContext *changedContext = notification.object;
    NSManagedObjectContext *childContext = self.mainQueueObjectContext;
    BOOL isParentContext = childContext.parentContext == changedContext;
    if (!isParentContext) return;

    //Collect the objectIDs of the objects that changed
    __block NSMutableSet *objectIDs = [NSMutableSet set];
    [changedContext performBlockAndWait:^{
        NSDictionary *userInfo = notification.userInfo;
        for (NSManagedObject *changedObject in userInfo[NSUpdatedObjectsKey]) {
            [objectIDs addObject:changedObject.objectID];
        }
        for (NSManagedObject *changedObject in userInfo[NSInsertedObjectsKey]) {
            [objectIDs addObject:changedObject.objectID];
        }
        for (NSManagedObject *changedObject in userInfo[NSDeletedObjectsKey]) {
            [objectIDs addObject:changedObject.objectID];
        }      
    }];

    //Refresh the changed objects
    [childContext performBlock:^{
        for (NSManagedObjectID *objectID in objectIDs) {
            NSManagedObject *object = [childContext existingObjectWithID:objectID error:nil];
            if (object) {
                [childContext refreshObject:object mergeChanges:YES];
                //NSLog(@"refreshing %@", [object description]);
            }
        }
    }];
}
   - (void)privateQueueObjectContextDidSaveNotification:(NSNotification *)notification {
    //NSLog(@"private Q MOC has saved");
    [self.mainQueueObjectContext performBlock:^{
        [self.mainQueueObjectContext mergeChangesFromContextDidSaveNotification:notification];
        // I had UI update problems which I fixed with mergeChangesFromContextDidSaveNotification along with obtainPermanentIDsForObjects: in the insertEntity call.
    }];

}

    /// When the MainMOC saves - the user has changed data. 
/// We save up into the privateQ as well at this point. 
- (void)mainQueueObjectContextDidSaveNotification:(NSNotification *)notification {
    NSLog(@"main Q MOC has saved - UI level only changes only please");
    [self.privateQueueObjectContext performBlock:^{
        NSError* error = nil;
        if (self.privateQueueObjectContext.hasChanges) {
            [self.privateQueueObjectContext save:&error];
            if (error)
            {
                NSLog(@"Save up error - the actual datastore was not updated : %@", error);
            }
        } else {
            //NSLog(@"No need to save");
        }
    }];
}
like image 120
Tom Andersen Avatar answered Nov 03 '22 02:11

Tom Andersen


Key piece of information:

The rest of the contexts will be children of the Main Queue Context

So your context for processing the downloaded data must be a child of the main context, and when you save that child you then save the main context, then you save the persistent context.

When you save the data processing context it notifies the parent, the main thread context, immediately, but you still need 2 saves to get the data onto disk.

like image 31
Wain Avatar answered Nov 03 '22 00:11

Wain