Developing For CloudKit- Part 2

Read Part 1 in series on Developing for CloudKit

It's been a week since my first post on developing an iOS app that uses CloudKit as a syncing solution to work with Core Data. Creating a mediation layer between Core Data and CloudKit has been no walk in the part but it's simpler than it first seemed.

My app, for now, doesn't need to make changes to its data objects. I'm constricting myself to the creation and deletion of objects in CloudKit. That decision has taken a load off my plate as I, for the first time, tackle sync totally by myself.

I started with converting NSManagedObjects to CKRecords. After the upload is complete, I use the (NSString *)recordName from CKRecord's (CKRecordID *)recordID returned from the completion handler and save that back to the NSManagedObject. This allows me to keep a local reference matching an NSManagedObject to the corresponding CKRecord stored in CloudKit. For local objects that don't have a valid recordName, I'll get to that in a bit.

Next is delete, very similar, I first delete the matching CKRecord in CloudKit (if there is one) and then delete my local copy once the server deletion is finished. CloudKit has extremely useful block callback and I highly suggest you use them!

Run that on 2 devices, add an object on the first device and... nothing happens. What?

We'll have to create a CKSubscription that will send a Push Notification to other devices signed into the same iCloud account when a CKRecord was uploaded, deleted, or updated in CloudKit. I won't go into detail about APNS here but it's not entirely complicated. Each notification will (for the most part) give you a reference to the changed object in its payload. Use that to update the corresponding NSManagedObject.

Now we have an app that syncs out locally saved data but for a device to get changes, it has to be on, open to the app. An app that isn't running will never know about changes. Next we have to add a way for our app to query the server when launched. This is a little more difficult than the steps I've covered so far.

Checking for changes that occurred while offline or in airplane mode will be done in 3 stages.

The first, to backtrack a bit, we need to keep track of objects that were deleted while offline. and delete them before the next steps. If we forget this, any offline deletions will be re-added later and create a frustrating problem for the user.

Second, we check for any CKRecords in CloudKit that we don't have a matching copy of on our local device. Start by getting a list of all CKRecordIDs, compare that to all objects we have locally, and remove the matches. Now, you're left with a list of CKRecords to download.

After that, check for local objects with a valid recordName property. Local objects with a recordName that no longer exists on the server means that object has been deleted on another device. You can safely remove those objects.

Once that's complete, you've successfully updated your device with changes made on CloudKit's server, but that's only half the story. We finish by reversing that process and updating CloudKit with changes to out local objects that happen while offline. 

This process is a little faster than the last. Start by creating an array of local objects that have a nil recordName. These have either never been synced or the upload wasn't successful. Either way, they need to be uploaded, do that now.

After that, you're done. (I say that knowing I've probably forgotten a step and will feel pretty foolish when my inbox fills up. Email Marco.)


I hope this has helped you, writing it all out helped me realize I forgot a step in my code. Bonus points if you can figure out what step I added at the last minute.

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