The new version of Sudoku Grab is going to be a universal app (iPhone and iPad) so it’s become even more important that user data sync across all the devices an user owns.
The types of data that we need to sync are: settings (colours, fonts etc..) and puzzle data. In this post I’m going to talk about settings and persisting settings in the cloud.
The mechanism that Apple recommends for doing this is the NSUbiquitousKeyValueStore and there is some pretty decent documentation on how to get it up and running in the Storing Preferences in iCloud document.
There’s a couple of thing to be aware of - the Key-Value data store is intended for small amounts of data:
The total space available in your app’s iCloud key-value storage is 1 MB per user. The maximum number of keys you can specify is 1024, and the size limit for each value associated with a key is 1 MB. For example, if you store a single large value of exactly 1 MB for a single key, that fully consumes your quota for a given user of your app. If you store 1 KB of data for each key, you can use 1,000 key-value pairs.
And:
The maximum length for a key string is 64 bytes using UTF8 encoding. The data size of your cumulative key strings does not count against your 1 MB total quota for iCloud key-value storage; rather, your key strings (which at maximum consume 64 KB) count against a user’s total iCloud allotment.
If you think you are going to be storing more than that amount of data then the key-value store is not for you and it’s probably worth asking yourself if you are really storing user defaults or some other kind of data.
Setting up for iCloud key-value store
The first thing to do is to enable your app for cloud synching. There seem to be too places where this needs to be done and I’m not sure if the xcode step also does the dev portal step - so I’d recommend doing both…
When you create your app Id you’ll need to switch on the iCloud App Services option - if you are updating an existing app then enabled the iCloud App Services and then I’d suggest regenerating your provisioning profile.
Now create/open your app select your target and modify the Capabilities section enabling iCloud and make sure you tick the “Use key-value store” checkbox.
Getting values from iCloud
You’re now ready to go! To use your iCloud settings you’ll need to get hold of the default NSUbiquitousKeyValueStore (this code is take from the Preferences and Settings Programming Guide).
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
This code gets hold of the default store and registers us to get updates when something changes in the store (e.g. another device modifies the values). We then synchronize the store with the cloud.
You should run this code as soon as possible when your app launches so that you get any changes immediately - you respond to these changes in the updateKVStoreItems notification:
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
// If a reason could not be determined, do not update anything.
if (!reasonForChange)
return;
// Update only for changes from the server.
NSInteger reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
// If something is changing externally, get the changes
NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
// do whatever you need to do when settings change
}
}
There are three possible reasons for receiving a change notification:
- NSUbiquitousKeyValueStoreServerChange - another device updated the values in iCloud.
- NSUbiquitousKeyValueStoreInitialSyncChange - slightly more complicated, only happens under these circumstances:
- You start the app and call synchronize
- Before iOS has chance to pull down the latest values from iCloud you make some changes.
- iOS gets the changes from iCloud
- NSUbiquitousKeyValueStoreQuotaViolationChange - you’re using it wrong and trying to store too much data.
- NSUbiquitousKeyValueStoreAccountChange - the user has changed to a difference iCloud account. What you should probably do is clear down the user defaults to their default settings and then copy accross any new values from the cloud.
Storing values in iCloud
You can treat the NSUbiquitousKeyValueStore just as you would NSUserDefaults:
[[NSUbiquitousKeyValueStore defaultStore] setString:@"Mr Blobby" forKey:@"name"];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];
As with NSUserDefaults it’s not neccessary to call synchronize every time you make a change (but calling synchronize will cause any changes to be sent to iCloud immediately).
Off line or No iCloud account
From my expeiments on my test devices if you are offline or if you have no iCloud account then values are still persisted - so it seems like these edge cases are not something to worry about.
Demo Project
I’ve put a very simple demo project up on github. Make sure you modifiy the app id to one that you have created on your own account and modified the code signing and provisioning profile settings.