Developing with CloudKit - First Impressions

I've recently started work on a new app that uses iOS 8's new CloudKit framework. I'm using CloudKit as the storage backend and Core Data for the local cache. So far, I'm impressed. CloudKit is a much-welcome departure and improvement over iCloud's Core Data Sync.

Before CloudKit, we relied on iCloud's Core Data sync mechanism to transfer data between devices. From the start, it was unreliable at the best of times. There was a fair bit of magic going on behind the scenes and the lack of transparency from Apple made debugging impossible. With new OS versions came significant increases in reliability but it problems persisted.

CloudKit aims to remove that magic and be as transparent as possible. In return, CloudKit is a transportation layer, sans magic. In essence, it handles sending data to iCloud, storing that data, and notifying your app where there's new data to be downloaded. The rest is up to you.

And that's actually a pretty great thing. This new app I'm working on simply takes an image, some strings and dates, and send that as a packaged object to the cloud. Setting up the sync part was actually pretty simple and took less than a day. It worked reliably, even with the network conditioner set to a poor network connection.

Next was implementing local offline storage. This is the tricky part.

I want to have the data saved onto the device for offline use and because some of the data is large and I respect my customer's data bills. When the device returns to the network, any changes are propagated via CloudKit.

Sounds a lot like sync, what's the difference?

Instead of creating a Core Data stack and then trying to get sync to work, I'm developing for CloudKit first. I'm getting Core Data to work nicely with CloudKit instead of trying to force CloudKit to play nicely with Core Data. This seemingly small distinction makes a large impact. Core Data is a robust framework and, thanks to Magical Record, is exceptionally flexible. CloudKit, on the other hand, is straight-forward and lean. 

On one side, we have CloudKit, the other the highly flexible Core Data. Now we need something to communicate between the two, like a translator. Let's call this the Intermediary.

With the ClouKit approach, Apple has left the Intermediary up to developers. This Intermediary is what I was referring to by the 'magic' of iCloud Data Sync. What happens when you try to create an Intermediary that has to cover all possible data structures and scale well? Turns out, not well.

So what I need is a simple, generic Intermediary that fits, but is not limited to, the data schemas I'm using. Data created on the device (NSManagedObject *) is saved via Core Data, the Intermediary is notified of the changes and converts the data to a CloudKit record (CKRecord *), and the record is handed off to Apple's servers via CloudKit. Any data in iCloud that's not on the device is brought down in the opposite direction converting the CKRecord back into, or creating, an NSManagedObject.

Because of the object type conversion, I have to keep a reference to later know which NSManagedObject corresponds to which CKRecord. If a new record was downloaded and I didn't know which local object to match it to, I would end up with endless duplicates. So do I save a reference to the NSManagedObject in the corresponding CKRecord or save a reference of the record in the object? 

For now, I'll try keeping a reference to the CKRecordID because UIDs on NSManagedObject change with each device. At least, that's the way it seems. More on that next time. I have my work cut out for me.

As Steve said, "The truth is in the Cloud". CloudKit is a lesson in treating cloud data as a first class citizen. 

Update: Screenshot++ is now available on the App Store with the CloudKit sync engine. Give it a spin!