Designing for Core Data in iCloud
Adopting iCloud Core Data storage makes the content in a shoebox-style app (like iPhoto) or a database-style app (like Bento) available on all of a user’s devices.
Although Core Data can work with a variety of persistent store file types, this chapter focuses on using Core Data with SQLite stores. One advantage of using SQLite in an iCloud-enabled app is that it can minimize network traffic by sending incremental changes to iCloud.
Core Data Sends Incremental Changes to iCloud
Each instance of your app, on each device attached to an iCloud account, maintains its own local Core Data store file. When data changes locally, Core Data writes change log files to your app’s default ubiquity container.
The change log files, not the store file, are uploaded to iCloud and downloaded to each of a user’s other devices. When a change log arrives from another device attached to the same iCloud account, Core Data updates your app’s local copy of the SQLite database, based on the received change log. iCloud and Core Data ensure that each local database is updated with the same set of changes.
Early in your app’s life cycle, register for the
NSPersistentStoreDidImportUbiquitousContentChangesNotification. When a Core Data store has imported changes from iCloud, it posts this notification. On receiving it, refresh the affected records and update the user interface.
When you use a SQLite Core Data store with iCloud, keep the following points in mind:
Place your SQLite Core Data store within a
<my_folder>.nosyncsubdirectory of one of your app’s ubiquity containers. This placement ensures that, if a user switches iCloud accounts, the system takes care of keeping each account’s data associated with the correct account.
Do not prepopulate the contents of a new SQLite store, such as to provide seed records the first time a user launches your app, by moving or copying an existing store file to a new location.
If your existing store file size is small, migrate its contents using the
If your store file is large, take care regarding the amount of memory consumed during migration, which is on the order of twice the size of the store. Transfer data in batches by using your app’s
NSManagedObjectContextinstance along with the
NSFetchRequestclass, setting the amount of data-per-batch with the
When using iCloud, the SQLite store on the device is a cache that represents data imported from the Core Data change log files. If there are pending transactions in change logs that Core Data has not yet processed, the store does not have the most current data.
To delete a store file, you must first tear down your Core Data stack. Then delete both the file package for the store and the directory containing the store’s transaction logs.
Each of these deletions requires you to perform a coordinated write operation using an
NSFileCoordinatorobject. For more information about deleting a SQLite store, see Using Core Data with iCloud Programming Notes.
Binary Stores for iCloud Apps Have Limitations
Core Data binary stores, and Core Data XML stores (OS X only), work differently with iCloud than do SQLite stores. Binary (and XML) store files are themselves transferred to the iCloud servers; so, whenever a change is made to the data, the system uploads the entire store and pushes it to all connected devices. For a large data set, or for a data set that changes frequently, these types of stores result in more data transfer to and from the iCloud servers and are consequently less efficient than using a SQLite store.
Because of this inherent reduction in efficiency, using binary or XML stores for all but small and infrequently-changing data sets results in excess network traffic. For small data sets that change infrequently, however, a binary store can work well in an iCloud-enabled app.
The rest of this chapter discusses only apps that use Core Data SQLite stores.
iOS Supports Core Data Documents in iCloud
A managed document is represented on disk as a file package, and includes its own Core Data store within the package. Using Core Data APIs, you can establish formal relationships among the elements in a managed document.
To use Core Data documents in iOS, use the
UIManagedDocument class, a concrete subclass of the abstract
UIDocument. You can use the
UIManagedDocument class directly or you can subclass it.
When using iCloud, Core Data attempts to resolve and merge changes between versions of a managed document. Just as with an iCloud-enabled Core Data app that does not use managed documents, register for the
NSPersistentStoreDidImportUbiquitousContentChangesNotification. When a managed document has imported data from iCloud, it posts this notification. On receiving it, refresh the affected records and update the document’s display.
When you create a managed document, you need to specify a unique location for its change log files so that later, if you need to delete the document, you can find those files (to delete them as well).
To set the location for a managed document’s change log files, specify a unique name (using the
NSPersistentStoreUbiquitousContentNameKey key) and a unique URL (using the
NSPersistentStoreUbiquitousContentURLKey key) in the managed document’s
For the URL, choose a location within the ubiquity container that you want to use, but outside of the
Documents subdirectory. The document saves the specified name and URL in its
DocumentMetadata.plist property list file, which is present inside the document’s file package.
To save a managed document in iOS, you can use any of the following three approaches, listed with the most recommended approach first:
Use the inherited Auto Save behavior provided by the
UIDocumentsuperclass. For most apps, this provides good performance and low network traffic.
If your app design requires explicit control over when pending changes are committed, use the
If you perform an explicit save operation in an iCloud-enabled app, be aware that you are generating additional network traffic—multiplied by the number of devices connected to the iCloud account.
If you have a specific case in which neither of the two preceding approaches works, such as importing a large quantity of data in the background, you can explicitly save the document’s managed object context.
A managed document has two contexts: The one it presents to you is a child of a second context that the document uses internally to communicate with the document’s Core Data store.
If you save only the document’s public context, you are not committing changes to the store; you still end up relying on Auto Save. To explicitly save a document’s managed object context, explicitly save both contexts. For more on this, read “Saving Changes” in Core Data Programming Guide.
To delete a managed document, you must first close the document. Then you must delete two directories, using coordinated write operations:
The directory for the Core Data change logs for the managed document
The managed document’s file package
To obtain the location of the directory containing the change log files, look at the document’s
DocumentMetadata.plist property list file. Retrieve the name and URL that you set when creating the document.
In OS X, the NSPersistentDocument Class Does Not Support iCloud
In OS X, Core Data integrates with the document architecture through the
NSPersistentDocument class. However, in OS X v10.8, instances of this class do not provide specific support for iCloud.
Design the Launch Sequence for Your iCloud Core Data App
When you adopt iCloud, take special care when designing the launch sequence for your app. The following factors come into play and you must account for them:
The user might or might not have previously indicated a preference to use iCloud in your app; the local instance of your app might or might not have already established its initial store in a ubiquity container.
As a first step in your launch sequence, read the local user defaults database using the shared
NSUserDefaultsobject. During operation of your app, use that object to save user choices that you’ll need on next launch.
iCloud might or might not be available.
The user might have set the device to Airplane mode, or the network might be otherwise unavailable. Determine how you want your app to behave when iCloud is unavailable. For example, you could allow the creation of new records but keep existing records read-only. If you allow editing of existing records, be prepared for additional data reconciliation when your app next connects to iCloud.
The user might log out of iCloud or switch to another account.
If a user logs out of iCloud, or switches to another account, the ubiquity containers for the previously-used account are no longer available to your app.
The local Core Data store might be newer or older than the store on another device owned by the same user.
During app launch, Core Data might need to reconcile the local store with change logs from iCloud. This can involve detection and resolution of duplicate records and conflicts. Testing is critical. To get started with some tips, refer to “Testing and Debugging Your iCloud App.”