5

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.

Tom Andersen
  • 7,132
  • 3
  • 38
  • 55

2 Answers2

2

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");
        }
    }];
}
Tom Andersen
  • 7,132
  • 3
  • 38
  • 55
1

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.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • No its the other way around. 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. – Tom Andersen Apr 01 '16 at 15:59
  • then you aren't following the guidance of the page you link to... using your approach you have to merge changes down to the main context after a save, it won't happen automatically... – Wain Apr 01 '16 at 16:04
  • I see - Marcus Zarra's page has a privateQ at the root. He then has a mainThreadQ under that. So that's what I am doing. But advises multiple privates below that main, which runs into real performance problems in a setup where most of the data coming into the system is from non user input. – Tom Andersen Apr 01 '16 at 16:12
  • I have a notification setup on the mainQ so I can tell when the user has edited something. That way I can reflect those edits up to the cloud. With a PrivateMOC under the mainMOC behind the scenes data manipulation shows up as thousands of notifications in the mainMOC. – Tom Andersen Apr 01 '16 at 16:16