Search code examples
ioscore-dataconcurrencynsmanagedobjectnsmanagedobjectcontext

Passing objects from a parent context in main queue to a child in private queue


I have an NSMutableArray property in a class where I keep reference to some managed objects of an NSManagedObjectContext which I called mainContext and that is associated to main queue. In this same class, I create a privateContext in a private queue and I set it as a child of the mainContext.

I'd like to pass the objects in my NSMutableArray (self.entities), which belong to the mainContext, to its child privateContext and, at the same time, keep a reference of such objects once they are in the privateContext in another array (self.tempEntities). I want to keep these references because I'll insert new objects later in privateContext and I'd need to easily know which of the objects that are at that moment in privateContext came from its parent mainContext.

I'm not sure if this way of doing this is correct:

for (MyEntity *entity in self.entities) { // this is main thread
   [self.privateContext performBlockAndWait: ^{
      [self.privateContext objectWithID:entity.objectID];
      [self.tempEntities addObject:entity];
    }];
}

Or this will cause any problem later. Or maybe is there another and better way to do this?

Thanks

EDIT: How will be the parent context updated in this case when the child is saved? I mean: the purpose of passing to the child context the objects in the parent, in my scenario, is that I need to compare in the child context its new objects with the ones that were passed from the parent. Then, maybe I need to delete some objects that came from the parent, and/or replace some of the objects that came from the parent with some of the news in child, and/or insert some of the new objects in child into the parent.

Would calling [self.privateContext save:nil]; replace all objects in parent context with the objects in the child, or how is the merge handled?

Thanks again


Solution

  • You are still accessing the main thread object in a background thread, which is not allowed. This could work (if it does not trigger an automatic fetch), but it is safer to get the object ID before entering the background thread.

    NSManagedObjectID objectID = entity.objectID;
    [self.privateContext performBlockAndWait: ^{
       MyEntity *bgEntity = [self.privateContext objectWithID:objectID]
       // do something with the entity, e.g. update it and save.
    }];
    

    Make sure you do everything you need to do in the background in the block. I would advise not to assign these objects to the controller (as you seem to be doing with self.tempEntities) where they are again available to the main thread. If they are accessed on the main thread, your app will crash.

    Another improvement would perhaps be to use just one context.

     NSArray *objectIDs = [self.entities valueForKeyPath:@"objectID"];
     [self.privateContext performBlockAndWait ^{
        for (NSManagedObjectID *objectID in objectIDs) {
            MyEntity *bgEntity = [self.privateContext objectWithID:objectID];
            // process...
        }
     }];
    

    As for updating the main context: when you save a child context, all it does is "push up" the changes to the parent context. The parent context is now aware of the changes, but of course it will not automatically update your arrays.

    There two avenues: the more complicated and risky one is to listen to the NSManagedObjectContextObjectsDidChangeNotification. In the notification handler, you could update your array and the table view. Still, this is risky because there is no memory or performance optimization etc.

    Better: use a NSFetchedResultsController and implement the NSFetchedResultsControllerDelegate methods to update your table or other data structures. You get lots of great functionality for free.