This post was originally published on Medium.
Recently, I decided it was time to upgrade an iOS app from Local Storage to a cloud-persisted solution. The app — a personal project called Go FlashCards that I built and released in conjunction with the then-new watchOS SDK — stored all of its data in
UserDefaults. I know… not great.
I had also received a couple of app reviews from users. One of the users requested that they be able to access their flashcards on all their iOS devices. Obviously, I thought that was a great idea. It also represented a challenge that I had not yet tackled when it came to iOS development.
These two issues — moving away from
UserDefaults and creating cloud persistence — were so intrinsically tied together I decided to tackle them both at once.
First, to solve the data storage issue, I decided to migrate from
UserDefaults to Realm. It was a no-brainer. I had worked with Realm in the past and found it very simple to setup models, handle migrations, and manage multi-threaded reads and writes.
The harder problem was the device data syncing issue. Realm offers a solution to help with this, but I needed a solution that was free to cheap (and also scalable for the future) because I wasn’t making any revenue off this app. I also wanted to avoid having to build a server application on Rails and spin it up in Heroku. There was Parse, but again, I did not want to worry about server concerns and scaling.
I did some digging around for free solutions and found CloudKit. I had heard about it before, but hadn’t had a use case for it until now. The platform is pretty robust and offers a basic feature set that can offer any app a backend service with minimal setup for basically nothing cost-wise.
Private, user-specific data counts against the User’s iCloud data quota instead of the App creator’s account quota. If you have data that is publicly accessible to all users, then that will be the only data that is counted against your quota. Quotas are pretty generous. 40 requests per second, 2 GB data transfer, 100 MB database storage, and 10 GB asset storage. Plus, Apple also uses CloudKit for all of their cloud services (Photos, Notes, etc.) so there is little risk of it going away anytime soon.
With CloudKit, you also don’t have to burden your users with creating an account, signing in, etc. If a user is already signed into their iCloud account, then you can save data to iCloud on their behalf. It just works.
Syncing Realm and CloudKit
The biggest challenge implementing CloudKit was keeping local data in sync with CloudKit. Luckily, Apple makes this easy enough with Subscriptions (
CKSubscription). Subscriptions are objects you save to a CloudKit database that trigger a push notification to user devices when something in the database has changed. You can subscribe to notifications for changes in different databases or database record zones. Here is a sample implementation of saving a subscription to a private database:
And registering for notifications and listening for updates in
Keeping Payloads Small
To avoid duplication of content and unnecessarily large payloads of data when pulling from CloudKit, Apple provides your application with a change token after a
CKFetchRecordZoneChangesOperation. You can use the change token as a cursor of sorts to make future requests, and CloudKit will only return data that has updated since the change token was provided. A change token is unique to every device.
Once you’ve processed the data from iCloud and saved it into Realm, updating the UI is pretty easy. Realm offers a way to notify a subscriber when data in a Realm has changed:
Of course, you will need to roll your own encoder and decoder for data sent to and from CloudKit. I’ll save those details for another blog post, but here’s a small preview. I created a protocol called
CloudKitSyncable that all my Realm objects conformed to:
With the help of CloudKit and Realm, I was able to setup a clean, cross-device syncing solution for users of Go FlashCards. At the same time, I improved data storage reliability without upfront cost or a complicated server setup.
So far the app is scaling well… I’ve increased the downloads of my app by a significant margin. I also haven’t run into any issues with data storage or network traffic quotas. CloudKit is awesome!
As a bonus, here is a gist of a VERY basic implementation for setting up subscription notifications and processing updates from CloudKit: