iCloud Fundamentals

From a user’s perspective, iCloud is a simple feature that automatically makes their personal content available on all their devices. To make your app participate in this “magic,” you need to design and implement your app somewhat differently, and for this you need to learn about your app’s roles when it participates with iCloud.

These roles, and the specifics of your iCloud adoption process, depend on your app. You design how your app manages its data, so only you can decide which iCloud supporting technologies your app needs and which ones it does not.

This chapter gets you started with the fundamental elements of iCloud that all developers need to know.

First, Provision Your Development Devices

To start developing an iCloud app, you must have an appropriate device provisioning profile and app ID. If you don’t already have these in place, learn about setting up a provisioning profile and app ID in “Provisioning Your App for Store Technologies” in App Distribution Guide.

iCloud Data Transfer Proceeds Automatically and Securely

When you adopt iCloud, the operating system initiates and manages uploading and downloading of data for the devices attached to an iCloud account. Your app does not directly communicate with iCloud servers and, in most cases, does not invoke upload or download of data. At a very high level, the process works as follows:

  1. You configure your app to gain access to special local file system locations known as ubiquity containers.

  2. You design your app to respond appropriately to changes in the availability of iCloud (such as if a user signs out of iCloud), and to changes in the locations of files (because instances of your app on other devices can rename, move, duplicate, or delete files).

  3. Your app reads and writes to its ubiquity containers using APIs that provide file coordination, as explained in “How iCloud Document Storage Works.”

  4. The operating system automatically transfers data to and from iCloud as needed.

In iOS, there is an exception to automatic iCloud data transfer. For the first-time download of an iCloud-based document in iOS, your app actively requests the document. You learn about this process in “How iCloud Document Storage Works.”

iCloud secures user data with encryption in transit and on the iCloud servers, and by using secure tokens for authentication. For details, refer to iCloud security and privacy overview. Key-value storage employs the same security as iCloud uses for "Documents in the Cloud,” as it is described in that document.

The Ubiquity Container, iCloud Storage, and Entitlements

To save data to iCloud, your app places the data in special file system locations known as ubiquity containers. A ubiquity container serves as the local representation of corresponding iCloud storage. It is outside of your app’s sandbox container, as shown in Figure 1-1.

Figure 1-1  Your app’s main ubiquity container in context

To enable your app to access ubiquity containers, you request the appropriate iCloud entitlements.

Request Access to iCloud Storage By Using Entitlements

Entitlements are key-value pairs that request specific capabilities or security permissions for your app. When the system grants your app iCloud entitlements, your app gains access to its ubiquity containers and to its iCloud key-value storage.

To use iCloud entitlements in an app, first enable entitlements in the Xcode target editor. Xcode responds by creating a .entitlements  property list file which you can see in the Xcode Project navigator.

You then request the specific iCloud entitlements you need for your app, associating them with a bundle identifier. (See “CFBundleIdentifier” in Information Property List Key Reference.)

There are two types of iCloud entitlement, corresponding to the two types of iCloud storage:

  • iCloud key-value storage. To enable key-value storage (appropriate for preferences or small amounts of data), select the iCloud Key-Value Store checkbox in the Xcode target editor. This requests the com.apple.developer.ubiquity-kvstore-identifier entitlement. Xcode responds by filling in the value for the entitlement with your app’s bundle identifier, which represents the iCloud key-value storage location for your app.

    If you look in the .entitlements file, you see the com.apple.developer.ubiquity-kvstore-identifier entitlement key along with a fully-qualified value, which includes your unique team identifier string. The entitlement value follows this pattern:

    <TEAM_ID>.<BUNDLE_IDENTIFIER>
  • iCloud document storage. To enable document storage (appropriate for user-visible documents, Core Data storage, or other file-based data), click the ‘+’ button below the iCloud Containers list in the Xcode target editor. Xcode responds by adding a first string to the list. The string identifies the primary ubiquity container for your app, and, by default, is based on your app’s bundle identifier.

    If you look in the .entitlements file, you see the com.apple.developer.ubiquity-container-identifiers key along with the fully-qualified default ubiquity container identifier value.

The first ubiquity container identified in the iCloud Containers list is special in the following ways:

  • It is your app’s primary ubiquity container, and, in OS X, it is the container whose contents are displayed in the NSDocument app-specific open and save dialogs.

  • Its identifier string must be the bundle identifier for the current target, or the bundle identifier for another app of yours that was previously submitted for distribution in the App Store and whose entitlements use the same team ID.

This chapter provides a full discussion about how to choose between key-value and document storage, in “Choose the Proper iCloud Storage API.”

Configuring a Common Ubiquity Container for Multiple Apps

In the Xcode target editor’s Summary tab, you can request access to as many ubiquity containers as you need for your app. For example, say you provide a free and paid version of your app. You’d want users, who upgrade, to retain access to their iCloud documents. Or, perhaps you provide two apps that interoperate and need access to each other’s files. In both of these examples, you obtain the needed access by specifying a common ubiquity container and then requesting access to it from each app.

bullet
To configure a common ubiquity container
  1. Pick one of your iCloud-enabled apps to serve as the primary app for the common ubiquity container.

    The app you pick can be the current one you are developing, or another app of yours submitted for distribution in the App Store and whose entitlements use the same team ID.

  2. In the first row of the Xcode target editor’s iCloud Containers list, enter the bundle identifier of your primary iCloud-enabled app.

    If you then look in the .entitlements property list file, you’ll see that Xcode has automatically qualified the identifier string by prefixing it with your team ID.

To retrieve a URL for a ubiquity container, you must pass the fully qualified string to the NSFileManager method URLForUbiquityContainerIdentifier:. That is, you must pass the complete container identifier string, which includes your team ID, that you see in the .entitlements property list file. You can pass nil to this method to retrieve the URL for the first container in the list.

For more information about how to configure entitlements, see “Configuring Store Technologies in Xcode and iTunes Connect” in App Distribution Guide.

Configuring Common Key-Value Storage for Multiple Apps

If you provide a free and paid version of your app, and want to use the same key-value storage for both, you can do that. The procedure is similar to that for configuring a common ubiquity container.

bullet
To configure common key-value storage for multiple apps
  1. Pick one of your iCloud-enabled apps to serve as the primary app for the common key-value storage.

    The app you pick can be the current one you are developing, or another app of yours submitted for distribution in the App Store and whose entitlements use the same team ID.

  2. In the Xcode target editor’s iCloud Key-Value Store field, enter the bundle identifier of your primary iCloud-enabled app.

    If you then look in the .entitlements property list file, you’ll see that Xcode has automatically qualified the identifier string by prefixing it with your team ID.

  3. Ensure that, in the Xcode projects for each of your other apps that are to use the common key-value store, the iCloud Key-Value Store field contains the bundle identifier of your specified, primary iCloud-enabled app.

Ubiquity Containers Have Minimal Structure

As you can see in Figure 1-2, the structure of a newly-created ubiquity container is minimal—having only a Documents subdirectory. This allows you to define the structure as needed for your app, such as by adding custom directories and custom files at the top level of the container, as indicated in Figure 1-2.

Figure 1-2  The structure of a ubiquity container

You can write files and create subdirectories within the Documents subdirectory. You can create files or additional subdirectories in any directory you create. Perform all such operations using an NSFileManager object using file coordination. See “The Role of File Coordinators and Presenters” in File System Programming Guide.

The Documents subdirectory is the public face of a ubiquity container. When a user examines the iCloud storage for your app (using Settings in iOS or System Preferences in OS X), files or file packages in the Documents subdirectory are listed and can be deleted individually. Files outside of the Documents subdirectory are treated as private to your app. If a user wants to delete anything outside of the Documents subdirectories of your ubiquity containers, they must delete everything outside of those subdirectories.

To see the user’s view of iCloud storage, do the following, first ensuring that you have at least one iCloud-enabled app installed:

A User’s iCloud Storage is Limited

Each iCloud user receives an allotment of complimentary storage space and can purchase more as needed. Because this space is shared by a user’s iCloud-enabled iOS and Mac apps, a user with many apps can run out of space. For this reason, to be a good iCloud citizen, it’s important that your app saves to iCloud only what is needed in iCloud. Specifically:

There may be times when a user wants to delete content from iCloud. Provide UI to help your users understand that deleting a document from iCloud removes it from the user’s iCloud account and from all of their iCloud-enabled devices. Provide users with the opportunity to confirm or cancel deletion.

The System Manages Local iCloud Storage

A user’s iCloud data lives on Apple’s iCloud servers, and a cache lives locally on each of the user’s devices, as shown in Figure 1-3.

Local caching of iCloud data allows a user to continue working even when the network is unavailable, such as when they turn on Airplane mode.

Figure 1-3  iCloud files are cached on local devices and stored in iCloud

Because the local cache of iCloud data shares space with the other files on a device, in some cases there is not sufficient local storage available for all of a user’s iCloud data. The system addresses this issue, automatically, by maintaining an optimized subset of files locally. At the same time, the system keeps all metadata local, thereby ensuring that your app’s users can access all their files, local or not. For example, the system might evict a file from its ubiquity container if that file is not being used and local space is needed for another file that the user wants now; but updated metadata for the evicted file remains local. The user can still see the name and other information for the evicted file, and, if connected to the network, can open it.

Your App Can Help Manage Local Storage in Some Cases

Apps usually do not need to manage the local availability of iCloud files and should let the system handle eviction of files. There are two exceptions:

  • If a user file is a) not currently needed and b) unlikely to be needed soon, you can help the system by explicitly evicting that file from the ubiquity container by calling the NSFileManager method evictUbiquitousItemAtURL:error:.

  • Conversely, if you explicitly want to ensure that a file is available locally, you can initiate a download to a ubiquity container by calling the NSFileManager method startDownloadingUbiquitousItemAtURL:error:. You learn more about this process in “App Responsibilities for Using iCloud Documents.”

Prepare Your App to Use iCloud

When a user launches your iCloud-enabled app for the first time, invite them to use iCloud. The choice should be all-or-none. In particular, it is best practice to:

Early in your app launch process—in the application:didFinishLaunchingWithOptions: method (iOS) or applicationDidFinishLaunching: method (OS X), check for iCloud availability by calling the NSFileManager method ubiquityIdentityToken, as shown in Listing 1-1.

Listing 1-1  Obtaining the iCloud token

id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];

Call this lightweight, synchronous method from your app’s main thread. The return value is a unique token representing the user’s currently active iCloud account. You can compare tokens to detect if the current account is different from the previously-used one, as explained in the next section. To enable comparisons, archive the newly-acquired token in the user defaults database, using code like that shown in Listing 1-2.

Listing 1-2  Archiving iCloud availability in the user defaults database

 if (currentiCloudToken) {
    NSData *newTokenData =
            [NSKeyedArchiver archivedDataWithRootObject: currentiCloudToken];
    [[NSUserDefaults standardUserDefaults]
            setObject: newTokenData
               forKey: @"com.apple.MyAppName.UbiquityIdentityToken"];
} else {
    [[NSUserDefaults standardUserDefaults]
            removeObjectForKey: @"com.apple.MyAppName.UbiquityIdentityToken"];
}

This code takes advantage of the fact that the ubiquityIdentityToken method conforms to the NSCoding protocol.

If a signed-in user enables Airplane mode, iCloud is of course inaccessible but the account remains signed in; the ubiquityIdentityToken method continues to return the token for the account.

If a user signs out of iCloud, such as by turning off Documents & Data in Settings, the ubiquityIdentityToken method returns nil. To enable your app to detect when a user signs out and signs back in, register for changes in iCloud account availability. In your app’s launch sequence, add an app object as an observer of the NSUbiquityIdentityDidChangeNotification notification, using code such as that shown in Listing 1-3.

Listing 1-3  Registering for iCloud availability change notifications

[[NSNotificationCenter defaultCenter]
    addObserver: self
       selector: @selector (iCloudAccountAvailabilityChanged:)
           name: NSUbiquityIdentityDidChangeNotification
         object: nil];

After obtaining and archiving the iCloud token, and registering for the iCloud notification, your app is ready to invite the user to use iCloud. If this is the user’s first launch of your app with an iCloud account available, display an alert by using code like that shown in Listing 1-4.

Listing 1-4  Inviting the user to use iCloud

if (currentiCloudToken && firstLaunchWithiCloudAvailable) {
    UIAlertView *alert = [[UIAlertView alloc]
                            initWithTitle: @"Choose Storage Option"
                                  message: @"Should documents be stored in iCloud and
                                                available on all your devices?"
                                 delegate: self
                        cancelButtonTitle: @"Local Only"
                        otherButtonTitles: @"Use iCloud", nil];
    [alert show];
}

(This code is simplified to focus on the sort of language you would display. In an app you intend to provide to customers, you would internationalize this code by using the NSLocalizedString (or similar) macro, rather than using strings directly.)

Store the user’s choice as a Boolean value in the user defaults database, from within a call to the alertView:didDismissWithButtonIndex: method. To do this, you can use code similar to that shown in Listing 1-2. This stored value lets you determine, each time your app launches, a value for the firstLaunchWithiCloudAvailable variable in the if statement’s condition (in Listing 1-4).

Although the ubiquityIdentityToken method tells you if a user is signed in to an iCloud account, it does not prepare iCloud for use by your app.

In iOS, make your ubiquity containers available by calling the NSFileManager method URLForUbiquityContainerIdentifier: for each of your app’s ubiquity containers.

In OS X v10.8 and later, the NSDocument class automatically calls the ubiquityIdentityToken method as needed.

Always call the URLForUbiquityContainerIdentifier: method from a background thread—not from your app’s main thread. This method depends on local and remote services and, for this reason, does not always return immediately. For example, you can use code such as that shown in Listing 1-5.

Listing 1-5  Obtaining the URL to your ubiquity container

dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
    myUbiquityContainer = [[NSFileManager defaultManager]
            URLForUbiquityContainerIdentifier: nil];
    if (myUbiquityContainer != nil) {
        // Your app can write to the ubiquity container
 
    dispatch_async (dispatch_get_main_queue (), ^(void) {
        // On the main thread, update UI and state as appropriate
    });
    }
});

This example assumes that you have previously defined myUbiquityContainer as an NSURL* instance variable in the class containing this code.

Handle Changes in iCloud Availability

There are circumstances when iCloud is not available to your app, such as if a user disables the Documents & Data feature or signs out of iCloud. If the current iCloud account becomes unavailable while your app is running or in the background, your app must be prepared to remove references to user iCloud files and to reset or refresh user interface elements that show data from those files, as depicted in Figure 1-4.

Figure 1-4  Timeline for responding to changes in iCloud availability

To handle changes in iCloud availability, implement a method to be invoked on receiving an NSUbiquityIdentityDidChangeNotification notification. Your method needs to perform the following work:

Choose the Proper iCloud Storage API

iCloud provides two different iCloud storage APIs, each for a different purpose:

Many apps benefit from using both types of storage. For example, say you develop a task management app that lets a user apply keywords for organizing their tasks. You would employ iCloud document storage for the task items, and key-value storage for the list of user-entered keywords.

If your app uses Core Data, either for documents or for a shoebox-style app like iPhoto, use iCloud document storage. To learn how to adopt iCloud in your Core Data app, see “Designing for Core Data in iCloud.”

If your app needs to store passwords, do not use iCloud storage APIs for that. The correct API for storing and managing passwords is Keychain Services, as described in Keychain Services Reference.

Use Table 1-1 to help you pick the iCloud storage scheme that is right for each of your app’s needs.

Table 1-1  Differences between document and key-value storage

Element

Document storage

Key-value storage

Purpose

User documents, complex private app data, and files containing complex app- or user-generated data.

Preferences and configuration data that can be expressed using simple data types.

Entitlement key

com.apple.developer. ubiquity-container-identifiers

com.apple.developer. ubiquity-kvstore-identifier

Data format

Files and file packages

Property-list data types only (numbers, strings, dates, and so on)

Capacity

Limited only by the space available in the user’s iCloud account.

Limited to a total of 1 MB per app, with a per-key limit of 1 MB.

Detecting availability

Call the URLForUbiquityContainerIdentifier: method for one of your ubiquity containers. If the method returns nil, document storage is not available.

Always effectively available. If a device is not attached to an account, changes created on the device are pushed to iCloud as soon as the device is attached to the account.

Locating data

Use an NSMetadataQuery object to obtain live-updated information on available iCloud files.

Use the shared NSUbiquitousKeyValueStore object to retrieve values.

Managing data

Use the NSFileManager class to work directly with files and directories.

Use the default NSUbiquitousKeyValueStore object to manipulate values.

Resolving conflicts

Documents, as file presenters, automatically handle conflicts; in OS X, NSDocument presents versions to the user if necessary. For files, you manually resolve conflicts using file presenters.

The most recent value set for a key wins and is pushed to all devices attached to the same iCloud account. The timestamps provided by each device are used to compare modification times.

Data transfer

In iOS, perform a coordinated read to get a local copy of an iCloud file; data is then automatically pushed to iCloud in response to local file system changes.

In OS X, iCloud files are always automatically pushed to iCloud in response to local file system changes.

Automatic, in response to local file system changes.

Metadata transfer

Automatic, in response to local file system changes.

Not applicable (key-value storage doesn’t use metadata).

User interface

None provided by iOS: Your app is responsible for displaying information about iCloud data, if desired; do so seamlessly and with minimal changes to your app’s pre-iCloud UI.

In OS X, NSDocument provides iCloud UI.

Not applicable. Users don’t need to know that key-value data is stored in iCloud.