Handling Core Data Store Change Notifications

Core Data will automatically switch stores when certain iCloud events happen, including the following:

  • User logs out of iCloud
  • User changes Documents & Data security settings
  • The ubiquity container is emptied either by deleting all the contents from Mac File Manager or by calling the removeUbiquitousContentAndPersistentStoreAtURL API

Note that there may be other scenarios, like migration(migratePersistentStore:), store model upgrades and rebuilding the store(NSPersistentStoreRebuildFromUbiquitousContentOption) where this store switching also occurs.

Regardless Apple’s WWDC2013 207 session provides example code along the lines of the following:

NSPersistentStoreCoordinatorStoresWillChangeNotification

-[NSManagedObjectContext save:]

-[NSManagedObjectContext reset:]

Store Removed by Core Data

NSPersistentStoreCoordinatorStoresDidChangeNotification

-[NSManagedObjectContext save:]

Now it is important to remember that these notifications are always sent from a background thread and the managedObjectContext you want to save is almost always going to be your main context which should only be called from the main thread because managedObjectContext’s are not thread safe.

Another not so obvious thing to remember is that Core Data actually waits for the storesWillChange notification method to return before removing the current store and switching.  This means that even though you need to call save on the main managedObjectContext using the main thread you need to do so synchronously to ensure Core Data does not remove the store before your main thread code gets run.

So we use the dispatch_sync to ensure the Core Data background thread waits until we have completed the save.

- (void)storesWillChange:(NSNotification*)n {

   // Now save the context

   // We MUST call this on the  main thread because managedObjectContext

   // is not thread safe and we MUST wait for it to complete to prevent

   // Core Data from switching the store while we are doing a save

   if ([NSThread isMainThread]) {

      NSError *error;

      if ([self.managedObjectContext hasChanges]) {

         [self.managedObjectContext save:&error];

      }

      [self.managedObjectContext reset];

   } else {

      dispatch_sync(dispatch_get_main_queue(), ^ {

         NSError *error;

         if ([self.managedObjectContext hasChanges]) {

            [self.managedObjectContext save:&error];

         }

         [self.managedObjectContext reset];

      });

   }

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s