Search
Follow
Recent Comments

Entries in iCloud (2)

Monday
Oct242011

Sync preference data with iCloud

One of the most interesting additions to iOS 5 was iCloud storage which makes it easy to sync data between apps running on different iOS and OS X devices. Effectively you can store key application data in the cloud and have it propagate to all instances of the app wherever they are running. The iCloud storage services supports two very different data models:

  • document storage
  • key value storage

In this post I will focus on using the key value store to sync a small amount of user preference data. This is the simplest and quickest way to start to use iCloud storage. I will follow up on using the document model and core data in future posts.

When to use Key-value Storage

The key-value store is intended to be used for small amounts of data, typically user preferences or other configuration data that does not change too frequently. It is not intended for syncing large amounts of user data such as documents or user created data. Each application is limited to a maximum of 64 KB of total key-value storage which also means individual keys cannot exceed 64 KB of storage.

It should also be noted that the iCloud key-value store is not intended to replace local user preference data. The local preference data is needed for when the device is offline or whenever the iCloud store is not accessible.

The Sample App - SyncMe

To illustrate how easy it is to add iCloud key value storage to an app I will adapt a simple standalone app to sync a configuration item stored as a user preference between instances of the app running on multiple iOS devices. The user interface for the app is shown below:

The basic idea is that touching one of the buttons changes the background colour of the view. The selected colour is stored as a user preference making it suitable for storing in the iCloud key-value store. Changing the colour in a running instance of the app should cause the preference and hence the background colour to be updated in all other instances of the app.

The app was built from the Xcode single view application template and is very simple so I will just summarise the key points before we start to look at what is required to add iCloud support.

The user interface is a single view with six UIButtons corresponding to the six possible background colours. The Touch Up Inside action of each button is connected to a single method in the view controller (UYLViewController) responsible for storing the selected colour in the user preferences and then actually changing the background colour of the view.

Each button has a tag value set in Interface Builder which corresponds to an enumerated type defined in the view controller as follows:

typedef enum { UYLblack, UYLblue, UYLgreen, UYLpurple, UYLred, UYLyellow } UYLcolor;

 

So for example the tag setting for the yellow button is as follows:

The method in the view controller which is connected to the button actions is shown below:

- (IBAction)colourChange:(id)sender {

 

  UYLcolor colorTag = [sender tag];
  NSNumber *color = [NSNumber numberWithInteger:colorTag];

 

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  [defaults setObject:color forKey:kUYLKVStoreBackgroundColorKey];

 

  [self changeBackgroundColor];

}

 

This method retrieves the tag from the UIButton which triggered the method call and after wrapping the value in an NSNumber object stores it in the apps local preferences store using the NSUserDefaults class. For more details on using NSUserDefaults you may want to take a look at Adding a settings bundle to an iPhone App (note that in this case we are not actually going to use a settings bundle as we do not need the user interface provided by the settings application).

The key used to store the preference is defined as an NSString in the class implementation:

NSString *kUYLKVStoreBackgroundColorKey = @”backgroundColor”;

 

Finally the method calls changeBackgroundColor to actually change the view background colour. It does this by setting the backgroundColour property of the view to the corresponding colour after retrieving the key from the user preferences:

- (void)changeBackgroundColor {

 

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  UYLcolor color = [defaults integerForKey:kUYLKVStoreBackgroundColorKey];

 

  switch (color) {
    case UYLblue:
      self.view.backgroundColor = [UIColor blueColor];
      break;
    case UYLgreen:
      self.view.backgroundColor = [UIColor greenColor];
      break;
    case UYLpurple:
      self.view.backgroundColor = [UIColor purpleColor];
      break;
    case UYLred:
      self.view.backgroundColor = [UIColor redColor];
      break;
    case UYLyellow:
      self.view.backgroundColor = [UIColor yellowColor];
      break;
    default:
      self.view.backgroundColor = [UIColor blackColor];
      break;
  }

}

 

The final piece of code in the view controller is to ensure we set the view background colour when the view first loads. We can do that by calling the changeBackgroundColor method from viewDidLoad:

- (void)viewDidLoad

{

  [super viewDidLoad];

[self changeBackgroundColor];

 

}

 

Of course this assumes we have set a default for the preference to cover the situation when the app runs for the first time. Actually this is not strictly necessary this time as an integer value will default to zero if it is not set in the user preferences which will give us a black background. However just to be explicit we can register a default value in the application delegate:

- (BOOL)application:(UIApplication *)application

                    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:0]
                                                           forKey:kUYLKVStoreBackgroundColorKey];
  [defaults registerDefaults:appDefaults];

 

   ...
   ...
  return YES;

}

 

So that is the app without iCloud support. Changing the background colour updates the locally stored user preference so that we maintain the same user selected colour each time the app runs. However if the app is running on multiple iOS devices the user must set the colour preference on each app since the local user preferences are not synchronised between devices.

Managing Entitlements

To get started with iCloud Key-value storage we must first modify the app entitlements file to add the com.apple.developer.ubiquity-kvstore-identifier. The easiest way to do that is from the Summary screen of the target in Xcode. Enabling the entitlements option will provide default values for the iCloud Key-Value Store identifier as well as the iCloud Containers key which is used when you are using iCloud document storage.

The default values are based on the application bundle identifier. If you are creating multiple versions of an app (e.g. a free and premium version or separate iPhone and iPad versions) you should use the same identifier in all versions of the app if you want them to all share the same store.

Saving Preferences to iCloud

With the entitlements correctly configured we can now save the user preferences to the iCloud Key-value store anytime we update the locally stored preferences. The code is very similar except that instead of using NSUserDefaults we use NSUbiquitousKeyValueStore. So for example the colourChange method used to update the local preferences now becomes:

- (IBAction)colourChange:(id)sender {

 

  UYLcolor colorTag = [sender tag];
  NSNumber *color = [NSNumber numberWithInteger:colorTag];

 

  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  [defaults setObject:color forKey:kUYLKVStoreBackgroundColorKey];

 

  NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
  if (store) {
    [store setObject:color forKey:kUYLKVStoreBackgroundColorKey];
  }
  [self changeBackgroundColor];

}

 

The defaultStore method returns us the shared key-value store object which we can then use to store the preference. Note that we are using the same key when storing the value in the key-value store that we use when storing in the local preference file. This is not required but it makes things easier when processing updates as we will see shortly.

Also note that we check we are actually returned the shared store object which ensures this code will also run on pre-iOS 5 versions of iOS. On earlier versions of iOS which do not support iCloud this code will not return a key-value store and hence is harmless. Since NSUbiquitousKeyValueStore is part of the Foundation framework you do not need to worry about weak-linking the framework since it is always present and the LLVM compiler takes care of weak-linking the class for us.

In this case we are only updating a single value. In situations where we need to update multiple values it is also possible to use an NSDictionary to set all values in a single operation. This ensures that either all values are updated together or none of them are updated. The only reason why the update might fail that is currently defined is if we exceed the 64 KB quota for the key-value store though that can of course change in the future.

Handling Updates from iCloud

To be notified whenever a key value store is modified or when we need to initially sync to iCloud we need to register for the NSUbiquitousKeyValueStoreDidChangeExternalNotification notification. It is best to do that as soon as the app is loaded so we will add the necessary code to the viewDidLoad method:

- (void)viewDidLoad

{

  [super viewDidLoad];
  [self changeBackgroundColor];

 

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

 

}

 

Anytime the iCloud Key-value store is modified we will get a call to the storeChanged method which can then process the change as follows:

- (void)storeChanged:(NSNotification*)notification {

 

  NSDictionary *userInfo = [notification userInfo];
  NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];

 

  if (reason) {

 

    NSInteger reasonValue = [reason integerValue];
    NSLog(@"storeChanged with reason %d", reasonValue);

 

    if ((reasonValue == NSUbiquitousKeyValueStoreServerChange) ||
        (reasonValue == NSUbiquitousKeyValueStoreInitialSyncChange)) {

 

      NSArray *keys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
      NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
      NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

 

      for (NSString *key in keys) {
        id value = [store objectForKey:key];
        [userDefaults setObject:value forKey:key];
        NSLog(@"storeChanged updated value for %@",key);
      }

 

      [self changeBackgroundColor];
    }
  }

}

 

The userInfo dictionary contained in the notification contains two objects of interest. The first, with key NSUbiquitousKeyValueStoreChangeReasonKey, provides the reason why the change notification was generated. There are two reasons that we are interested in:

  • NSUbiquitousKeyValueStoreServerChange: this value indicates that the key-value store has changed in the cloud most likely because another device has sent a new value.
  • NSUbiquitousKeyValueStoreInitialSyncChange: this value occurs the first time the app runs and has yet to be synced with iCloud.

In both of these cases we can retrieve the second object in the userInfo dictionary with key NSUbiquitousKeyValueStoreChangedKeysKey. This object is an NSArray containing the keys of all objects that have been changed. These changed objects can then be retrieved from the defaultStore and written to the local user preferences using the traditional NSUserDefaults object. Since we are using the same key to store values in iCloud and in the local preferences file we can take a shortcut by looping through each of the keys and use the setObject:forKey method to write to the local preferences file. Finally with the change stored locally we call the changeBackgroundColor method to actually change the view background colour.

There is one more reason that can currently be returned in the notification. NSUbiquitousKeyValueStoreQuotaViolationChange indicates that we have exceeded the 64 KB limit of the key-value store. In this example I ignore this error but you could log the error or I suppose report it to the user. My guess is that if you are in danger of exceeding the 64 KB limit you are probably using the wrong approach.

Provisioning the App

To actually test iCloud storage you will need to deploy it to an actual device - as far as I can tell it will not work in the simulator. Before you deploy you will need to generate a provisioning profile (or update an existing profile) to use an App ID that is enabled for iCloud:

Testing the Sync

Once you have the app correctly provisioned we can deploy it to several iOS devices and see how well it works. The first iOS device we run the app on will write values to the store once we start changing the colour. If we then run the app on a second device the background colour starts as black (the default local preference setting) and then after a few seconds we receive the first notification (actually an NSUbquitousKeyValueStoreInitialSyncChange) and the colour changes to the choice we made on the first device.

In practise you will find that it can take anywhere from a few seconds to more than a minute for changes to propagate. The iCloud guidelines from Apple do indicate that the Key-value store is intended for small amounts of configuration data that are not expected to change frequently. In fact they even imply that if you make frequent calls to the store this will slow down future updates:

If the apps on a device make frequent changes to the key-value store, the system may defer the synchronization of some changes in order to minimize the number of round trips to the server. The more frequently apps make changes, the more likely it is that later changes will be deferred and not show up on other devices right away.

Wrapping Up

I hope you have found this initial look at iCloud useful. The key-value store is the easiest iCloud access method to add to an App and I expect many apps to start to make use of it to sync user preferences. As always the full Xcode project for the example app can be downloaded here or browsed in my public Github repository.

Monday
Jun202011

Thoughts on iOS 5

Now that WWDC is a fading memory (when will those videos of the sessions show up for those of us who could not go…) I wanted to comment on the two things I am most excited about with iOS 5. Of course everything is under NDA so my comments are limited to what was in the keynote or has otherwise been made public by Apple.

iCloud

A lot of details about iCloud were revealed in the keynote but I am interested in what it might mean for iOS developers.

Consider the case where I have a reading application or a game where I already save things like bookmarks or game progress to a local plist file on the device. If I have the same app installed on both my iPhone and my iPad I have no way to have the app stay in sync between those two devices. The same applies if I have an app that creates content, currently the only way to make that content turn up on all devices is to use the cumbersome mechanism provided by iTunes or an external service such as dropbox.

There are a great many iOS apps which are effectively stand-alone apps that fit this model. Since the apps do not need to connect to a central server there is no requirement for the app developer to deploy and run a hosted server which costs real money. However the downside is that each instance of the app running on a particular device is completely ignorant of other iOS devices the user may have.

For apps that already rely on a web service or some other hosted server I suspect iCloud will not make much difference but for all other apps I expect it to have significant impact. In fact I would expect all iOS apps to allow for basic settings and preference information to be sync’ed between all iOS devices the user has. This means that if I have been using an app on my iPhone and then decide to also install it on my iPad that the app starts up on the iPad with exactly the same settings and app context that it has on the iPhone and that the two devices stay in sync.

If you are looking at moving you apps to iOS 5 (and why would you not) then I think building in a basic level of iCloud support for settings and app state should be one of the first things you target.

Automatic Reference Counting (ARC)

This announcement, tucked away on a single keynote slide, during the WWDC keynote was really totally unexpected. Objective-C 2.0 has language support for garbage collection but it can only be used with Cocoa apps running on OS X. For iOS we have been stuck with performing our own memory management using retain, release and autorelease. Once you get the hang of the Objective-C conventions I do not find managing the retain/release cycle to be that hard but it remains a big hurdle for new iOS developers to jump over and a source of memory leaks and app crashing bugs.

I had long assumed that as iOS devices get more powerful we would eventually arrive at a point where Apple would also support garbage collection on iOS unifying the two Cocoa platforms. I was not expecting that to happen this year but I was guessing or hoping it would be coming soon. Apple though would not be Apple if they could not occasionally pull a rabbit out of the hat and do something unexpected.

You can find a lot of detail on ARC on the LLVM compiler site - see Automatic Reference Counting. Be warned that it is not easy reading and you may want to wait until the WWDC session videos are available. In brief, and again staying on the right side of the NDA, it is a compiler feature that promises to automatically take care of the retaining/releasing of objects for us. The promise for an iOS developer is that you will not need to worry about retaining and releasing objects manually. The LLVM compiler is now smart enough to manage the objects for you and automatically insert the retains/release methods for you at compile time.

If you have used the Clang language analyser you will have already seen how smart the LLVM parser has become. It is already capable of highlighting errors with retain and release so I guess it was the logical next step to just let the compiler insert the correct memory management methods into the code for us.

When Objective-C 2.0 came along and introduced properties with the dot syntax it created some controversy for long time Objective-C programmers who felt it hid what was happening under the covers and could be misused. I guess the same will be true with ARC with many experienced developers preferring to stay with manual memory management. It is too early to pass judgement but in theory if ARC lives up to its promise it could both simplify and speed up iOS apps without the need to implement garbage collection which as Steve would say is magical.