- Setting Up an iCloud-Compatible Project
- How iCloud Works
- iCloud Container Folders
- Recipe: Trying Out iCloud
- Working with UIDocument
- Recipe: Subclassing UIDocument
- Metadata Queries and the Cloud
- Handy Routines
- Recipe: Accessing the Ubiquitous Key-Value Store
- Recipe: UIManagedDocument and Core Data
- Summary
Recipe: Accessing the Ubiquitous Key-Value Store
iCloud’s key-value store lets you share small amounts of state information between devices using a ubiquitous property list. “Small” means you are limited to a total of 256Kb per key-value store and 64Kb per individual value.
Those limits mean you won’t be using this utility for sharing images and other large data items. They’re meant to save state such as the last page read, the default account name, or other such tiny bits of information that can propagate between devices to enhance the seamless movement of the user from one device to the next.
How Key-Value Stores Work
Your application’s ubiquitous key-value store is nothing more than a distributed defaults dictionary that can be reached from devices registered to the same iCloud account. This store isn’t meant to replace NSUserDefaults; it augments it by allowing you to develop applications that enable users to resume work regardless of the device used.
Imagine your user begins editing a ubiquitous document on a certain device and later launches your app on a second one. The key value store would let you load the right document (by storing the file name) and scroll it to the current position (by storing the current offset), so the user resumes interaction at the point that he or she left off.
If you want to use more extensive shared defaults, you can create those defaults in a property list file outside the ubiquitous Documents folder. This has two advantages. First, it lets you store whatever data you need without regard to the key-value store’s quota limitations. Second, it hides those defaults from the user. Data stored outside Documents cannot be directly seen or edited by the user but they remain visible to your application.
Accessing the Key-Value Store
You can retrieve the shared store from its class method, namely [NSUbiquitousKeyValueStore defaultStore]. Once retrieved, query it like you would user defaults, e.g.:
kv = [NSUbiquitousKeyValueStore defaultStore]; switchView.on = [kv boolForKey:@"switchIsOn"];
Similarly, set its value using the same dictionary-style method calls. As with user defaults, you must synchronize your changes to publish them from memory to the persistent iCloud store. Although the system may call synchronize automatically, make a habit of manually performing the synchronization. This ensures that your changes are pushed immediately rather than with a possible several-second delay.
- (void) toggleSwitch: (UISwitch *) aSwitch { // Send switch update out to cloud [kv setBool:aSwitch.isOn forKey:@"switchIsOn"]; [kv synchronize]; }
The previous sample code snippets implement a ubiquitous switch. When the user interacts with the switch on one device, the change propagates to all devices. In order for that change to register, your application must subscribe to a notification that informs it about key-value store updates.
Subscribing to Key-Value Notifications
Your application will want to know when the ubiquitous store has updated item values. Subscribe it to NSUbiquitousKeyValueStoreDidChangeExternallyNotification. This notification lets you determine when the store has updated and adjust your in-app settings to match.
The notification’s user info dictionary provides a list of affected keys as well as a reason why the update occurred. NSUbiquitousKeyValueStoreServerChange means the values simply updated. In addition, your application may be notified of a quota violation (NSUbiquitousKeyValueStoreQuotaViolationChange) or that local changes were discarded because they could not be sent to the server for an initial sync (NSUbiquitousKeyValueStoreInitialSyncChange).
Recipe 18-3 builds callbacks based on these various scenarios, allowing a client class to add informal callbacks. If you don’t care which key was updated here, just implement the kvStoreUpdated: callback, which is sent on any value change. The kvStoreUpdatedForKeys: callback sends an array of affected keys for more nuanced responses.
Recipe 18-3 Subscribing to Key-Value Update Notifications
[[NSNotificationCenter defaultCenter] addObserverForName: NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification __strong *notification) { NSDictionary *userInfo = [notification userInfo]; NSUInteger reason = [[userInfo objectForKey: NSUbiquitousKeyValueStoreChangeReasonKey] intValue]; NSArray *keys = [userInfo objectForKey: NSUbiquitousKeyValueStoreChangedKeysKey]; // Perform updates only if there is a delegate to listen if (!delegate) return; if (reason == NSUbiquitousKeyValueStoreServerChange) { if (keys.count == 1) #SAFE_PERFORM_WITH_ARG(delegate, @selector(kvStoreUpdatedForKey:), [keys lastObject]); else if (keys.count) SAFE_PERFORM_WITH_ARG(delegate, @selector(kvStoreUpdatedForKeys:), keys); SAFE_PERFORM_WITH_ARG(delegate, @selector(kvStoreUpdated), nil); } else if (reason == NSUbiquitousKeyValueStoreInitialSyncChange) SAFE_PERFORM_WITH_ARG(delegate, @selector(kvStorePerformedInitialSync), nil); else if (reason == NSUbiquitousKeyValueStoreQuotaViolationChange) SAFE_PERFORM_WITH_ARG(delegate, @selector(kvStoreViolatedQuota), nil); }];