Sorry for the long delay in responding!
My implementation doesn't treat local data as just a local cache, or destroy any data on a fresh sync. CloudKit sync is totally optional in my app, so a user can turn it off or never use it, but would still want the data they see on their device preserved even if they do so.
So, when I get an expired change token, my app does a "unify" operation where it tries to match all the local records and the cloud records together. If there are cloud records that don't exist locally, it creates them locally; if there are local records that don't exist in the cloud, it creates them in the cloud.
The goal is to never, ever accidentally delete something and to give the user the ability to work without CloudKit. In essence, if there is no change token, it treats this like the first sync it's ever done with iCloud, where it needs to make sure that all data is merged into a single source of truth - rather than an incremental update where we are just making a few changes.
However, there's an issue with this approach: if the user has two devices fully in sync, then deletes items on one device, and waits a few weeks before syncing their second device, the second device will:
- Get an expired change token.
- Begin the merge process.
- Create records in iCloud for all of the local records deleted on the first device (since they've been on the second device all along)
Unfortunately, step 3 would include any items that the user deleted on their first device: they were on both devices when everything was in sync, and because the second device never got the chance to run an incremental sync and pull in those deletions, it has no idea that the items should be gone.
I could easily resolve this problem if, on the second device, even with a change token of nil, CloudKit told me about every deletion that had happened since its last connection to the server. I could just go through that list and apply the deletions before running the merge algorithm. But if it only includes deletions made on the same device, my app doesn't have a way of knowing what the user has deleted since the last sync.
Hopefully that's a little clearer than my first explanation. Basically, I want to know if the original answer or the second answer in this thread is correct: does it include everything since the last communication with CloudKit, or only things deleted on the same device?
If the answer is the latter, I'm not really sure how to prevent deleted items from coming back like this.