Saturday, April 26, 2014

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
To retrieve a URL for a ubiquity container, you must pass the fully qualified string to the NSFileManager methodURLForUbiquityContainerIdentifier:. 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

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 Documentssubdirectory 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:
  • In iOS, open Settings. Then navigate to iCloud > Storage & Backup > Manage Storage.
  • In OS X, open System Preferences. Then choose the iCloud preference pane and click Manage.

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:
  • DO store the following in iCloud:
    • User documents
    • App-specific files containing user-created data
    • Preferences and app state (using key-value storage, which does not count against a user’s iCloud storage allotment)
    • Change log files for a SQLite database (a SQLite database’s store file must never be stored in iCloud)
  • DO NOT store the following in iCloud:
    • Cache files
    • Temporary files
    • App support files that your app creates and can recreate
    • Large downloaded data files
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 theNSFileManager 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:
  • Use iCloud exclusively or use local storage exclusively; in other words, do not attempt to mirror documents between your ubiquity container and your sandbox container.
  • Never prompt the user again about whether they want to use iCloud vs. local storage, unless they delete and reinstall your app.
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 thefirstLaunchWithiCloudAvailable 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:
  • Call the ubiquityIdentityToken method and store its return value.
  • Compare the new value to the previous value, to find out if the user signed out of their account or signed in to a different account.
  • If the previously-used account is now unavailable, save the current state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
    If you want to allow users to continue creating content with iCloud unavailable, store that content in your app’s sandbox container. When the account is again available, move the new content to iCloud. It’s usually best to do this without notifying the user or requiring any interaction from the user.

Choose the Proper iCloud Storage API

iCloud provides two different iCloud storage APIs, each for a different purpose:
  • Key-value storage is for discrete values such as preferences, settings, and simple app state.
    Use iCloud key-value storage for small amounts of data: stocks or weather information, locations, bookmarks, a recent documents list, settings and preferences, and simple game state. Every app submitted to the App Store or Mac App Store should take advantage of key-value storage.
  • Document storage is for user-visible file-based content, Core Data storage, or for other complex file-based content.
    Use iCloud document storage for apps that work with presentations, word-processing documents, diagrams or drawings, or games that need to keep track of complex game state. If your document-based app needs to store state for each document, do so with each document and not by using key-value storage.
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
PurposeUser 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 keycom.apple.developer. ubiquity-container-identifierscom.apple.developer. ubiquity-kvstore-identifier
Data formatFiles and file packagesProperty-list data types only (numbers, strings, dates, and so on)
CapacityLimited 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 availabilityCall 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 dataUse an NSMetadataQuery object to obtain live-updated information on available iCloud files.Use the shared NSUbiquitousKeyValueStore object to retrieve values.
Managing dataUse the NSFileManager class to work directly with files and directories.Use the default NSUbiquitousKeyValueStore object to manipulate values.
Resolving conflictsDocuments, 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 transferIn 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 transferAutomatic, in response to local file system changes.Not applicable (key-value storage doesn’t use metadata).
User interfaceNone 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.

No comments:

Post a Comment