Notifying the Application of Changes

Once we’ve made changes on disk, we need to notify the contexts of these changes.

In this particular example, doing so isn’t strictly necessary. Most likely the user interface hasn’t touched any of these objects yet. If the objects haven’t been loaded into memory, there’s no risk of a conflict. However, it’s best that we don’t assume they haven’t been loaded yet. Users can be very clever.

There are two basic ways to notify our NSManagedObjectContext instances of the changes. We can reset each object individually in each NSManagedObjectContext that it might be associated with, or we can use the new API that was added in iOS 9.0. Let’s look at the harder way first.

Manual Object Refreshing

If the situation calls for it, we can instruct each instance of NSManagedObject to refresh individually. This might make sense if we have a view that’s observing one specific instance of an entity or we have a user interface that’s watching a small subset of objects.

Batch/PPRecipes/PPRDataController.m
 -​ (​void​)manuallyRefreshObjects:(NSArray*)oIDArray;
 {
  NSManagedObjectContext *moc = [self managedObjectContext];
  [moc performBlockAndWait:^{
  [oIDArray enumerateObjectsUsingBlock:^(NSManagedObjectID *objectID,
  NSUInteger index, BOOL *stop) {
  NSManagedObject *object = [moc objectRegisteredForID:objectID]​;
 if​ (!object || [object isFault]) ​return​​;
  [moc refreshObject:object mergeChanges:YES]​;
  }]​;
  }];
 }

The -manuallyRefreshObjects: method accepts an NSArray of NSManagedObjectID instances and walks through that array. Because this method is going to be working with the NSManagedObjectContext that’s on the main queue, we want a guarantee that our code will also be executed on the main queue. Therefore, we start by executing the code in a -performBlock: to ensure we’re on the correct queue.

Inside the block we iterate over the array of NSManagedObjectID instances and retrieve a reference to the associated NSManagedObject. Note that we’re using -objectRegisteredForID: in this method. -objectRegisteredForID: will only return a non-nil result if the object is registered with the referenced NSManagedObjectContext. If it isn’t referenced, we certainly don’t want to load it, so this method is a perfect fit. From that call we need to see if we got an object back and that it isn’t a fault. If it is a fault, we don’t need to refresh it because the values aren’t in memory.

Once we confirm the NSManagedObject is registered and isn’t a fault, we call -refreshObject: mergeChanges:, which will force the object to reload from the persistent store.

That’s a fair amount of work for each individual NSManagedObject in the array. Fortunately, there’s an easier way.

Remote Notifications

As part of the update for iOS 9.0 and OS X 10.11, the Core Data team gave us a new class method to handle this situation. We can now call +[NSManagedObjectContext mergeChangesFromRemoteContextSave: intoContexts:] to handle changes that occurred outside our Core Data stack.

The method accepts a dictionary of arrays of NSManagedObjectID instances (either true NSManagedObjectID objects or NSURL representations of them) and also accepts an array of NSManagedObjectContext instances.

Like traditional NSManagedObjectContextDidSaveNotification calls, we can pass in objects that are in three possible states: inserted, updated, and deleted.

Batch/PPRecipes/PPRDataController.m
 -​ (​void​)mergeExternalChanges:(NSArray*)oIDArray ofType:(NSString*)type
 {
  NSDictionary *save = ​@​{type : oIDArray};
 
  NSArray *contexts = @[[self managedObjectContext], [self writerContext]];
 
  [NSManagedObjectContext mergeChangesFromRemoteContextSave:save
  intoContexts:contexts];
 }

Note in this method that we aren’t concerned with what queue things are being executed on. The API call handles that for us. Also note that we’re able to update all of the contexts that exist in our application at once.

This method will basically do the same thing that we did in our manual method. But this method will handle it for every context we give it, and it’s faster than our manual method. This is the recommended way to consume remote notifications. With this method, our original bulk update call can easily notify the rest of the application of the changes.