What is iCloud?
Apple uses iCloud as an umbrella term for all their online services and APIs, so it’s easy to get confused about what iCloud actually is. There’s iCloud e-mail, which is just IMAP. Calendars and contacts are synchronized using CalDAV and CardDav. Notes use the IMAP server of your email account. All these are completely independent of the iCloud APIs available to iOS and OS X developers.
There are actually three different iCloud APIs developers can use:
Ubiquitous Key Value Store:
NSUbiquitousKeyValueStoreis a simple key-value store with an API modelled after
NSUserDefaults. While it can sometimes be a bit slow to propagate across all your devices, this works pretty well. You use this to store discrete values, for example preferences or highscores or similar simple data.
Document storage: when using document storage your app has access to a special folder called ubiquity container. Files stored in this container are synchronized across your devices. It’s not quite as simple as using the normal filesystem since the files might change at any moment, or even get deleted from under you.
UIDocumenthandles a lot of this file coordination for you, so you’ll almost always want to use that.
Core Data storage: this builds on top of document storage. Your SQLite database isn’t stored directly inside the ubiquity container1, instead each time you save your
NSManagedObjectContexttransaction logs are generated that represent the changes and these logs are then stored in the ubiquity container. Other devices download and import these transaction logs, replicating all the changes.
Ubiquity Containers and Transaction Logs
Document storage and Core Data storage both use the ubiquity container, but the key-value store is completely independent of this.
You can inspect the ubiquity containers of the apps installed on your iOS or OS X devices by logging in to developer.icloud.com or by browsing to
~/Library/Mobile\ Documents on your Mac.
Drew McCormack does a great job explaining the format of the transaction logs in a blog post. Note that you should never touch these logs directly. Removing transaction logs manually will break synchronization, unless you delete all the transaction logs and delete the SQLite databases on each device, basically resetting all the data.
Problems with Core Data iCloud Storage
Core Data in iCloud has a number of problems and hard to deal with use cases. These problems mostly stem from the fact that the API is presented to developers as a black box: you set up your persistent store with ubiquity options and everything else happens automatically in the background. There’s no way to hook into the underlying machinery dealing with the transaction logs, so when something goes wrong there’s not much you can do in your application.
When the user resets the container of your app using the iCloud settings, you need to completely drop and rebuild your Core Data stack, but there is no built-in way to detect these resets. If you don’t detect and handle it properly, synchronization just stops working. Not good. The usual method to deal with detecting resets is to use the document storage API to store an additional “sentinel” file in your ubiquity container. Since the transaction logs and the sentinel file are all stored in the same ubiquity container, you know that if the sentinel file disappears the transaction logs must have been deleted too.
You can handle the user logging out of iCloud or logging in to a different account the same way since the ubiquity container is tied to the account. Alternatively, on iOS 6 you can register for the
NSUbiquityIdentityDidChangeNotification notification to detect this. Note that this notification does not fire for data resets.
Another problem is that setting up your Core Data stack can sometimes be very slow when using iCloud. The call to
addPersistentStoreWithType:configuration:URL:options:error: sometimes blocks for 5 - 30 seconds. Even if you load your Core Data stack in a background queue, you can’t access the stored data until this finishes. This makes for a horrible user experience. (Thread on devforums.apple.com)
Yet another problem is that on iOS the transaction logs are not downloaded unless your app is in the foreground. So not only is the first launch of your app sometimes slow because of the time it takes to setup the Core Data stack, new data is not immediately available, but slowly trickles in as the transaction logs are downloaded and imported. (Bugreport on openradar.appspot.com)
It would also be really helpful if there was a complete sample project by Apple. Session 227 from WWDC 2012 covers the basics, but the presented sample project is incomplete and doesn’t properly handle iCloud resets.
iOS 6 brought some improvements to Core Data iCloud storage and Apple developers have acknowledged that there are still problems. Hopefully by the time iOS 7 is released Core Data iCloud storage will be a rock solid API. It isn’t unusable right now, but be prepared to run into problems when using it in your app.
You can store the database inside the ubiquity container, but it must be inside a
*.nosyncfolder. Apple recommends you do this so that the database is automatically removed when the user logs out of iCloud or logs in to a different account. ↩
You can contact me on Twitter or at email@example.com