Keeping application data synchronized across devices is a complex and daunting task. Fortunately, that is exactly why Apple built iCloud. In this Tuts+ Premium series, you will learn how iCloud works and how your applications can seamlessly share data across multiple devices.
Also available in this series:
- Working with iCloud: Introduction
- Working with iCloud: Key-Value Storage
- Working with iCloud: Document Storage
- Working with iCloud: Core Data Integration
In the previous installments of this series, we refactored our sample application to switch from iCloud Key-Value Storage to iCloud Document Storage. iCloud's Document Storage is much more flexible than Key-Value Storage and therefore better suited for data heavy applications. Core Data is a persistence framework built for applications that manage large amounts of data and complex data models. Over the years, Core Data has earned its stripes and is known as a reliable and flexible framework for data persistence. This series wouldn't be complete without discussing Core Data and its integration with iCloud.
Core Data Application Types
In terms of Core Data, there are two types applications, (1) document-based applications and (2) library-style applications. Document-based applications, such as Apple's Pages application, manage documents backed by a Core Data store. Library-style applications, such as the iPhone's built-in music application, make use of a single persistent store that is used throughout the application.
In this tutorial, I will discuss both application types and sketch a high-level overview of how each application type can integrate with iCloud. Core Data is a very broad topic and one of the more advanced aspects of Mac and iOS development. This tutorial will only scratch the surface of what is possible and the integration options available with iCloud.
UIManagedDocument is a concrete subclass of
UIDocument and is tailored to fit the needs of document-based Core Data applications. For document-based applications,
UIManagedDocument offers the best of two worlds, (1)
UIDocument's ease of use and (2) built-in Core Data support. It is worth mentioning that
UIManagedDocument can be used even if there is no need for iCloud integration. Even though
UIManagedDocument is often mentioned in the context of iCloud, it is useful to take a moment and underline the benefits of the
UIManagedDocument class itself.
Just like its superclass,
UIManagedDocument makes document management easy and straightforward. The key difference with
UIDocument is that
UIManagedDocument is aimed specifically at Core Data. Both
UIManagedDocument have a lot to offer out of the box. Remember from the previous tutorial that
UIDocument features automatic saving, and loading and saving operations are handled on a background queue while abstracting away the nitty gritty details. If you decide to use Core Data in a document-based application, it is recommended to make use of
UIManagedDocument even if iCloud is not on your application's feature list.
Before the introduction of
UIManagedDocument, the Core Data stack was traditionally set up and configured in the application delegate. This is no longer necessary when using
UIManagedDocument. One of the niceties of the
UIManagedDocument class is its built-in Core Data stack. After initializing an instance of
UIManagedDocument, a complete Core Data stack is available to you.
UIManagedDocument's interface exposes a managed object context as well as the persistent store coordinator and the managed object model. Configuring the persistent store coordinator is as easy as supplying a dictionary with options just like you do when manually initializing a persistent store coordinator.
As its name implies,
UIManagedDocument is a concrete subclass of
UIDocument, which means that it can be used as is, without the need to subclass
UIDocument. Behind the scenes,
UIManagedDocument automatically aggregates managed object models it finds in your application bundle and prepares a Core Data stack for you to work with. If your application has more complex requirements then it's up to you to subclass
UIManagedDocument to meet those requirements.
Ease of Use
Anyone familiar with Core Data knows that that changes are committed to the persistent store by sending the managed object context a save: message. This is no longer necessary when using
UIManagedDocument. Not only is this not necessary, it should be avoided. The reason is that, under the hood,
UIManagedDocument manages a private managed object context. This private managed object context manages a child managed object context, which is the managed object context exposed through
UIManagedDocument's public interface. By sending the child managed object context a save: message, the changes are only committed to the parent managed object context and not to the persistent store that is managed by the parent managed object context.
In short, you should use the familiar
UIDocument methods to save changes to the persistent store, the rest is taken care of behind the scenes. There is a reason why
UIManagedDocument inherits from
Library-based applications, such as the iPhone's built-in music and photos applications, usually manage one Core Data stack with one persistent store coordinator and one persistent store.
Under The Hood
You might be wondering how Core Data works with iCloud under the hood. This depends on the type of store you use, that is, (1) a SQLite or (2) a binary (atomic) store. In the majority of use cases, it is recommended to opt for SQLite. However, if you deal with small amounts of data then a binary store is an option as well. In the light of iCloud, the major disadvantage of a binary store is that every change results in the entire store being replaced. Not only is this inefficient, it can potentially result in significant performance implications if the store grows in size. In the rest of this discussion, I will focus on SQLite as the backing store.
With SQLite as the backing store, the major advantage is that changes are propagated incrementally instead of replacing the entire store with every committed change. To understand how Core Data and iCloud work together it is important to know that the SQLite database is not stored in iCloud. Instead, every change is registered in so-called transaction logs and those logs are used to propagate changes across devices. The transaction logs are lightweight and result in a fine-grained, fast, and efficient synchronization process. The transaction logs are stored in a directory in the iCloud container. The exact location can be specified by setting the
NSPersistentStoreUbiquitousContentURLKey key in the options dictionary passed to the persistent store coordinator.
Another vital aspect of synchronization is keeping the user interface updated. When using Core Data this can be accomplished by registering the appropriate controllers as an observer for
NSPersistentStoreDidImportUbiquitousContentChangesNotification notifications, which are sent whenever a remote change is propagated. Based on the changes, your application can update the user interface to reflect the changes committed to its local persistent store.
Another key advantage of Core Data is the granular control you have over your data and this is especially true with respect to merging data, an essential part of working with iCloud. Thanks to this granularity, which is only possible thanks to the use of transaction logs, most of the merging is done automatically by Core Data.
The common thread throughout this series has been how your applications can benefit from iCloud and how to integrate with iCloud. I want to end this series by exposing a few caveats that you may want to watch out for when adding iCloud support to an application. In the second and third installment of this series, I showed you how to use
NSFileManager's URLForUbiquityContainerIdentifier: method to get a reference to a specific iCloud container for storing data in iCloud. In our examples, we always assumed that iCloud was enabled on the devices we worked with, but
this will not always be the case. It is therefore important to have a fallback solution in place for situations when iCloud is not enabled. Below are two examples to better illustrate these caveats.
The first example is when a user doesn't have an iCloud account or she hasn't signed in with her iCloud account on the device. This means that your application is unable to access the iCloud container it intends to store data in. It becomes even more complex when she enables iCloud after having used your application for a few weeks. The user will expect her data to be synchronized across her devices. In other words, you will need a way to transfer the data from the application sandbox to the iCloud container.
The second example focuses on data persistence. What happens when a user has enabled iCloud on his device, but decides after a few weeks to sign in with a different iCloud account? The data stored in the iCloud container is no longer available to the user since a different iCloud account is being used. This is a security measure put in place by Apple. Data is linked to an iCloud account, not to a device. The data is therefore not accessible when a different iCloud account is used. It is rare that one user has multiple iCloud accounts, but it is important to be aware of this possibility and the consequences it can have.
I hope that this series about iCloud has given you a good idea of what iCloud is and what it can do for you as a developer. The basic idea behind iCloud is simple and adopting iCloud is relatively easy if your application's data model isn't too complex. However, this final installment has shown that iCloud integration can become very complex when the complexity of your application's data model increases.