CKSyncEngine
Discover how
CKSyncEnginecan help you sync people’s CloudKit data to iCloud. Learn how you can reduce the amount of code in your app when you let the system handle scheduling for your sync operations. We’ll share how you can automatically benefit from enhanced performance as CloudKit evolves, explore testing for your sync implementation, and more.
The documentation:
The sync engine uses an opaque type to track its internal state, and it’s your responsibility to persist that state to disk and make it available across app launches so the engine can function properly.
For example, if you delete an object while offline, you can send that change to the engine and let it keep track of whether it’s been pushed to the server. In theory, you don’t have to manage tombstones yourself as long as you persist the engine’s state. When you add or change objects, the state stores just the IDs so it shouldn’t grow too huge.
There’s some sample code, but it’s frustrating in that the conflict resolution is rather basic. I think you actually can access the ancestorRecord to do a three-way merge, but they don’t show that.
I removed
CKSyncEngineand did it all myself solving the problems we needed solved. Maintaining a synced database of items with unique IDs supporting cascade deletes and automatically handling conflicts without much participation from the server which knows nothing of our needs.[…]
One of the key problems we had was how CloudKit replays deletion tombstones going way back. Tapestry would download feed items, make a
CKRecord, and put it in iCloud to sync to other devices. Then later when the item gets old, we delete them. Over time, with busy feeds, you’re looking at hundreds of expiring items per day for some people.[…]
Those replays are a total waste of time but it’s just how it works. It could take hours to restore the relatively small number of actually current records as CloudKit replays dead records the new install never had in the first place. Thousands and thousands of them.
I had to rearchitect everything to redesign it around this one unchangeable behavior.
You could probably fetch the initial data manually, using CloudKit directly, before initializing CKSyncEngine. Then it would take a while for it to catch up, but at least the app would be usable. It seems like the engine should just handle this better, though.
Christian Selig (Mastodon, tweet):
I’ve had a lot of fun working with
CKSyncEngineover the last month or so. I truly think it’s one of the best APIs Apple has built, and they’ve managed to take a very complex topic (cloud syncing) and make it very digestible and easy to integrate, without having to get into the weeds ofCKOperationand whatnot like you had to in previous years.More interesting for a blog post, perhaps, I also had a fair few questions going into it (having very little CloudKit knowledge prior to this), and I thought I’d document those questions and the corresponding answers, as well as general insights I found to potentially save a future
CKSyncEngineuser some time, as I really couldn’t find easy answers to these anywhere (nor did modern LLMs have any idea).[…]
But you should not have multiple
CKSyncEngineinstances managing a single private database (I naively tried to do this to have a nice separation of concerns between different types of data in the app). The instances trip over each othre very quickly, with it not being clear which instance receives the sync events.[…]
In this [the case of
quotaExceeded] Apple pauses the queue until the user frees up space or buys more (or after several minutes, specified byretryAfterSeconds) but does not add your item back, which seems weird to me, so just add it back. But you also can’t just add it back, as that would put it at the end of the queue, so you have to insert it back at the beginning of the queue so it’s the next item that will be retried (since it just failed). Only, there’s no API for this, so grab all the items in the queue, then empty the queue, then re-add all items back to the queue with your failed item at the front.
CKSyncEnginehas greatly simplified the integration with CloudKit and the error handling though. It was way more difficult before, I would say almost impossible before.[…]
There are several issues here. The first one is not related to
CKSyncEnginebut to the replay API of CloudKit. The second one is that you have options when fetching changes to fix this issue (Scope+prioritizedZoneIds) but it is supposed you designed your schema accordingly. And it didn’t exist withCKFetchRecordZoneChangesOperationonly withCKSyncEngine… The third issue is that you can’t useZoneOptions.desiredKeys
See also: Harmony, which uses CKSyncEngine on GRDB (via Aaron Pearce).
Previously: