Core Data for iOS: Part 4 – Core Data syncing with iCloud

This is the fourth and last part of the blog post series about Core Data in iOS. In case you were not following along, here is a short description of the previous articles:

  • Part 1: setting up the application data model
  • Part 2: polling data from the persistent store using NSFetchedResultsController and basic data updating
  • Part 3: data model versioning and migration

The topic of the current article is Core Data synchronization using iCloud. It’s a pretty controversial topic, because even if people expect nowadays that an app should be able to run on any device and allow them to pick up from where they left off last time they ran the app, iCloud data synchronization doesn’t always work as good as Apple wants us to believe. Sure, this article was published two years ago, and there have been major improvements in iCloud synchronization with the releases of iOS7 and iOS8. But the lack of control and predictability of the synchronization process still is a pain in the neck for developers.

My take on using iCloud synchronization with Core Data is that you can make it work pretty reliably for relatively simple use cases, but if you want control on every aspect of the process, you should preferably use the CloudKit API available in iOS8.

This article explains how to set up an app to use iCloud synchronization for the Core Data stack. As you’ll see, it doesn’t require much work if we keep things at a general level.

Add iCloud entitlements

This article is written for iSO8 and I used Xcode 6 for the screenshots. The setup instructions may change in the future iOS / Xcode releases.

If you want your app to use iCloud, you have to explicitly enable the iCloud support in the target settings. You also need a paid Apple developer account to get the necessary entitlements from developer portal. In Xcode, while the project is open, go to the target settings, display the Capabilities tab and switch on the iCloud service. Make sure the iCloud Documents box is checked, this is the only option you need to get Core Data synchronization:

iCloud entitlements for Core Data sync

Xcode should then automatically get the entitlements from you developer account and insert them in a .entitlements file it creates and adds to the project.

Configure the persistent store for iCloud sync

Because it’s possible to use multiple persistent stores in the same app and synchronize only some of them, informing iCloud which persistent store it has to synchronize is a matter of setting the NSPersistentStoreUbiquitousContentNameKey and NSPersistentStoreUbiquitousContentURLKey in the options dictionary when we add the persistent store to the persistent store coordinator using the -addPersistentStoreWithType:configuration:URL:options:error: method.

The NSPersistentStoreUbiquitousContentNameKey key is mandatory and it sets the name of the persistent store in iCloud. The NSPersistentStoreUbiquitousContentURLKey key can be omitted in iOS7 and later, because the API automatically creates the directory structure where it keeps the database and the log files related to the persistent store.

Unlike the versions iOS6 and older, the -addPersistentStoreWithType:configuration:URL:options:error: API returns very quickly and we don’t have to invoke it on a background queue to avoid blocking the main thread. The same applies to NSFileManager -URLForUbiquityContainerIdentifier: method, which returns nil if iCloud is not enabled on the device, in which case we shouldn’t try to set up the iCloud-backed persistent store.

How iCloud synchronization actually operates is pretty simple to explain, but far more complicated to get it to work properly. Each time a NSManagedObject instance is created, modified or deleted on a device, the operation is added to a transactions log file in .plist format, and the log files are automatically transferred by iCloud between the different devices connected to the same iCloud user account. The persistent store coordinator monitors the incoming log files and triggers NSNotification. The app is able to handle the updates by registering for these notifications.

Migrate the local persistent store to iCloud

It’s not uncommon to ship an application with some preloaded data. If this data is already inside a SQLite database, it’s easy to create a persistent store backed by this database, add it to the persistent store coordinator and migrate it to iCloud using the migratePersistentStore:toURL:options:withType:error: API.

When the preloaded data store is migrated, it creates a transactions log file with insert operations for each entity in data store. If the migration is performed on each device the application runs on, a transactions log is created each time and this generates data duplicates. To avoid this problem, we should migrate the preloaded store only once, on the first device the app is launched on. The solution I came up with to detect if the preloaded store was already migrated is probably not the most elegant. I took advantage of the NSFileManager -startDownloadingUbiquitousItemAtURL: method which returns YES if the specified file exists on the iCloud. So, when the app is launched for the first time, I create a flag file, move it to the iCloud and perform the preloaded data store migration. In the subsequent app launches, the flag file existence is detected and the migration is not performed.

Register for iCloud notifications

As explained before, the persistent store coordinator launches several types of NSNotifications when it detects incoming transactions from iCloud.

We have to subscribe to these notifications to be able to react when the data is synchronized.

We’re particularly interested in three types of notifications:

  • NSPersistentStoreCoordinatorStoresWillChangeNotification: triggered before iCloud is switched off on the device or before the iCloud user account changes
  • NSPersistentStoreCoordinatorStoresDidChangeNotification: triggered after iCloud is switched off on the device or after the iCloud user account changes
  • NSPersistentStoreDidImportUbiquitousContentChangesNotification: triggered when an incoming transaction log is detected, containing data updates

The next code sample creates the persistent store coordinator, adds the iCloud-backed persistent store, migrates the preloaded data store and subscribes to iCloud update notifications:

Handle the iCloud notifications

In the most common scenario, the actions that should be performed for each type of iCloud update notifications are:

  • for NSPersistentStoreCoordinatorStoresWillChangeNotification: save all pending updates in the managed object context before iCloud is switched off. You would also probably want to migrate the iCloud store to a local store.
  • for NSPersistentStoreCoordinatorStoresDidChangeNotification: switch to a local persistent store if iCloud was turned off, or to the cloud persistent store for the new iCloud user account if the user logged in to another account. Also update the user interface to reflect the persistent store switching.
  • for NSPersistentStoreDidImportUbiquitousContentChangesNotification: merge the updates received from iCloud into the managed object context. This is done by calling the -mergeChangesFromContextDidSaveNotification: method on the managed object context and passing the NSNotification as a argument. The userInfo attribute of the notification contains the managed object updates.

The following code is typical for basic iCloud notification handling. The entire Xcode project is available for download on GitHub.

Conclusion

This article wraps up the four blog post series on Core Data which covered the general aspects of using this powerful technology. I will probably have other opportunities to explore more deeply some specific Core Data facets.

Before closing up the article, I recommend using Magical Record library in your Core Data apps, which is wrapper greatly simplifying the interactions with the Core Data stack. It will make your code cleaner and more readable, and it also contains many powerful features dealing with multi-threading and other Core Data advanced aspects.

 

Catalin Rosioru