Storing Preferences in iCloud

An app can use the iCloud key-value store to share small amounts of data with other instances of itself on the user’s other computers and iOS devices. The key-value store is intended for simple data types like those you might use for preferences. For example, a magazine app might store the current issue and page number being read by the user so that other instances of the app can open to the same page when launched. You should not use this store for large amounts of data or for complex data types.

To use the iCloud key-value store, do the following:

  1. In Xcode, configure the com.apple.developer.ubiquity-kvstore-identifier entitlement for your app.

  2. In your code, create the shared NSUbiquitousKeyValueStore object and register for change notifications.

  3. Use the methods of NSUbiquitousKeyValueStore to get and set values.

Key-value data in iCloud is limited to simple property-list types (strings, numbers, dates, and so on).

Strategies for Using the iCloud Key-Value Store

The key-value store is not intended for storing large amounts of data. It is intended for storing configuration data, preferences, and small amounts of app-related data. To help you decide whether the key-value store is appropriate for your needs, consider the following:

If you are using the key-value store to share preferences, one approach is to store the actual values in the user defaults database and synchronize them using the key-value store. (If you do not want to use the preferences system, you could also save the changes in a custom property-list file or some other local storage.) When you change the value of a key locally, write that change to both the user defaults database and to the iCloud key-value store at the same time. To receive changes from external sources, add an observer for the notification NSUbiquitousKeyValueStoreDidChangeExternallyNotification and use your handler method to detect which keys changed externally and update the corresponding data in the user defaults database. By doing this, your user defaults database always contains the correct configuration values. The iCloud key-value store simply becomes a mechanism for ensuring that the user defaults database has the most recent changes.

Configuring Your App to Use the Key-Value Store

In order to use of the key-value store, an app must be explicitly configured with the com.apple.developer.ubiquity-kvstore-identifier entitlement. You use Xcode to enable this entitlement and specify its value for your app, as described in Adding iCloud Support in App Distribution Guide.

When you enable key-value store, Xcode automatically fills in a default value for the containers field that is based on the bundle identifier of your app. For most apps, the default value is what you want. However, if your app shares its key-value storage with another app, you must specify the bundle identifier for the other app instead. For example, if you have a lite version of your app, you might want it to use the same key-value store as the paid version.

Enabling the entitlement is all you have to do to use the shared NSUbiquitousKeyValueStore object. As long as the entitlement is configured and contains a valid value, the key-value store object writes its data to the appropriate location in the user’s iCloud account. If there is a problem attaching to the specified iCloud container, any attempts to read or write key values will fail. To ensure the key-value store is configured properly and accessible, you should execute code similar to the following early in your app’s launch cycle:

NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
          selector:@selector(updateKVStoreItems:)
          name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
          object:store];
[store synchronize];

Creating the key-value store object early in your app’s launch cycle is recommended because it ensures that your app receives updates from iCloud in a timely manner. The best way to determine if changes have been made to keys and values is to register for the notification NSUbiquitousKeyValueStoreDidChangeExternallyNotification. And at launch time, you should call the synchronize method manually to detect if any changes were made externally. You do not need to call that method at other times during you app’s execution.

For more information about how to configure entitlements for an iOS app, see Adding Capabilities in App Distribution Guide.

Accessing Values in the Key-Value Store

You get and set key-value store values using the methods of the NSUbiquitousKeyValueStore class. This class has methods for getting and setting preferences with scalar values of type Boolean, long long, and double. It also has methods for getting and setting keys whose values are NSData, NSDate, NSString, NSNumber, NSArray, or NSDictionary objects.

If you are using the key-value store as a way to update locally stored preferences, you could use code similar to that in Listing 3-1 to coordinate updates to the user defaults database. This example assumes that you use the same key names and corresponding values in both iCloud and the user defaults database. It also assumes that you previously registered the updateKVStoreItems: method as the method to call in response to the notification NSUbiquitousKeyValueStoreDidChangeExternallyNotification.

Listing 3-1  Updating local preference values using iCloud

- (void)updateKVStoreItems:(NSNotification*)notification {
   // Get the list of keys that changed.
   NSDictionary* userInfo = [notification userInfo];
   NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
   NSInteger reason = -1;
 
   // If a reason could not be determined, do not update anything.
   if (!reasonForChange)
      return;
 
   // Update only for changes from the server.
   reason = [reasonForChange integerValue];
   if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
         (reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
      // If something is changing externally, get the changes
      // and update the corresponding keys locally.
      NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
      NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
      NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
 
      // This loop assumes you are using the same key names in both
      // the user defaults database and the iCloud key-value store
      for (NSString* key in changedKeys) {
         id value = [store objectForKey:key];
         [userDefaults setObject:value forKey:key];
      }
   }
}

Defining the Scope of Key-Value Store Changes

Every call to one of the NSUbiquitousKeyValueStore methods is treated as a single atomic transaction. When transferring the data for that transaction to iCloud, the whole transaction either fails or succeeds. If it succeeds, all of the keys are written to the store and if it fails no keys are written. There is no partial writing of keys to the store. When a failure occurs, the system also generates a NSUbiquitousKeyValueStoreDidChangeExternallyNotification notification that contains the reason for the failure. If you are using the key-value store, you should use that notification to detect possible problems.

If you have a group of keys whose values must all be updated at the same time in order to be valid, save them together in a single transaction. To write multiple keys and values in a single transaction, create an NSDictionary object with all of the keys and values. Then write the dictionary object to the key-value store using the setDictionary:forKey: method. Writing an entire dictionary of changes ensures that all of the keys are written or none of them are.