Using Core Data with iCloud Release Notes

Contents:

Using Core Data with iCloud

When you create an application that uses Core Data and iCloud, there are two orthogonal choices you need to make:

  1. What sort of application do you want to create?

  2. What store type do you want to use?

There are broadly speaking two types of Core Data-based application that integrate with iCloud:

  1. Library-style applications, where the application usually has a single persistent store, and data from the store is used throughout the application.

    Examples of this style of application are Music and Photos.

  2. Document-based applications, where different documents may be opened at different times during the lifetime of the application.

    Examples of this style of application are Keynote and Numbers.

If you’re already using Core Data in iOS, then you probably have a library-style application. For this, you are encouraged to use the SQLite store with Core Data’s iCloud integration.

UIKit provides support for document-based applications that use Core Data through the UIManagedDocument class—a concrete subclass of the UIDocument class that represents a document. For library-style applications, you generally set up the Core Data stack as you would if you were not using iCloud. For both library-style and document-based applications, however, you must appropriately configure the persistent store(s).

How you configure the store depends on what type of store you want to use. There are two store types to choose from:

  1. SQLite.

  2. Atomic (the binary store type) or a custom atomic store derived from NSAtomicStore.

Use the SQLite Store for Large Datasets and Fine-Grained Change Notification

You usually use the SQLite store, because it offers efficient per-record change propagation and corresponding change notifications.

In iOS 5, Core Data includes enhancements to support efficient integration of the SQLite store with iCloud. The principal new feature is that each change you commit to a persistent store is recorded in discrete transaction log files that are stored in iCloud, rather than keeping the persistent store file in the cloud directly. This means that change propagation through iCloud is efficient (the alternative would be to push the whole SQLite file for each change). There are a few consequences to this, though:

Use the Atomic Store for Small Data Sets When Changes are “Wholesale”

With an atomic store, changes are not recorded in separate transaction log files. There are therefore potential performance implications because iCloud must propagate changes to the whole store file. You should usually only use an atomic store for small data sets. Moreover, instead of receiving notifications of changes to individual records and having the ability to merge these changes, you are simply told that the store has changed via the standard NSFilePresenter and (in document-based applications) UIDocument conflict resolution methods. You must then accept or reject the changes wholesale.

If you use an atomic store, you must put the persistent store file in a location where changes to it will be propagated by iCloud. All saves and reads must be performed in a file coordination block.

Guidance for Library-style Applications

In a library style application, you usually have a single Core Data stack with a single persistent store coordinator and a single persistent store.

If you use the SQLite store, you:

You can observe NSPersistentStoreDidImportUbiquitousContentChangesNotification notifications posted by the persistent store; this allows you to react to changes on a per-record basis.

If you use the atomic store:

Guidance for Document-based Applications

In a document-based application, you represent the document using an instance of UIManagedDocument. The document instance automatically aggregates managed object models it finds in the application’s main bundle (subclasses can override the managedObjectModel property to customize this behavior) and manages the Core Data stack for you.

A managed document uses two managed object contexts. The first context is created on a private queue so that changes can be saved asynchronously. The second is a child of the first, created on the main queue. The child context is the context that the document returns from the managedObjectContext method. This has a couple of implications:

An instance of UIManagedDocument uses a SQLite store by default; you can specify a different store type by creating a subclass of an instance UIManagedDocument and overriding persistentStoreTypeForFileType: to return the appropriate type.

Documents that use a SQLite store

If you use a SQLite store, you need to provide a unique identifier for the store. You set the identifier using the NSPersistentStoreUbiquitousContentNameKey in the setPersistentStoreOptions: dictionary for the managed document. You should also provide a ubiquitous content URL using the NSPersistentStoreUbiquitousContentURLKey. (The default URL is based on the bundle identifier. If you provide a custom URL for the transaction logs, you can share documents across applications with different identifiers. Typically you specify a directory called TransactionLogs in the ubiquity container.) So that you can retrieve it later if necessary (for example, when deleting a document), UIManagedDocument saves the store’s ubiquitous content name in a property list file named DocumentMetadata.plist in the document package.

To summarize details about the document structure and location:

  • The document package contains a property list file, DocumentMetadata.plist.

  • The root element in the DocumentMetadata.plist property list is a dictionary. One of the dictionary keys is NSPersistentStoreUbiquitousContentNameKey. The corresponding value is the name of the directory that contains the transaction logs for the document.

  • Transaction logs for a document are stored in a directory: <Value of NSPersistentStoreUbiquitousContentURLKey>/<Value of NSPersistentStoreUbiquitousContentNameKey>.

To ensure that the persistent store itself is not synced by iCloud: when you set a value for the NSPersistentStoreUbiquitousContentNameKey, UIManagedDocument puts the persistent store in a .nosync directory inside the document package. If you make use of additional content (using the writeAdditionalContent:toURL:originalContentsURL:error: method), you must make sure that the document directory is not a package. Typically you give the document directory an extension that is not recognized as a document extension.

To respond to changes propagated from iCloud, you observe NSPersistentStoreDidImportUbiquitousContentChangesNotification notifications posted by the document's persistent store; this allows you to respond to changes on a per-record basis.

To find documents, you use a metadata query as you would for other ubiquitous content. The query returns the DocumentMetadata.plist file inside the document packages.

To open a document that uses the SQLite store, you must retrieve the value of the NSPersistentStoreUbiquitousContentNameKey from the DocumentMetadata.plist file and set that content name key in the persistentStoreOptions dictionary before you open the document with openWithCompletionHandler:.

To delete a document that uses the SQLite store, you must also delete the transaction logs directory for the document. You do this as a coordinated operation using NSFileManager.

Schema Migration

Core Data applications will not sync with other versions using different schemas. With the SQLite store, Core Data only supports lightweight migration on iCloud. When using light weight migration, different versions of the application will not sync with each other. When an older version upgrades, it will catch up and previously too-new versions will merge in the older version’s changes.

Because atomic stores overwrite the entire file, if you update the schema the store will not be able to be read by clients using a previous schema.

Limitations on Functionality

When used with iCloud, there are several limitations with the SQLite store at this time: