CKSyncEngine save existing CKRecord

I have transitioned to CKSyncEngine for syncing data to iCloud, and it is working quite well. I have a question regarding best practices for modifying and saving a CKRecord which already exists in the private or shared database.

In my current app, most CKRecords will never be modified after saving to the database, so I do not persist a received record locally after updating my local data model. In the rare event that the local data for that record is modified, I manually fetch the associated server record from the database, modify it, and then use CKSyncEngine to save the modified record.

As an alternative method, I can create a new CKRecord locally with the corresponding recordID and the modified data, and then use CKSyncEngine to attempt to save that record to the database. Doing so generates an error in the delegate method handleSentRecordZoneChanges, where I receive the local record I tried to save back inevent.failedRecordSaves with a .serverRecordChanged error, along with the corresponding server CKRecord. I can then update that server record with the local data and re-save using CKSyncEngine. I have not yet seen any issues when doing it this way.

The advantage of the latter method is that CKSyncEngine handles the entire database operation, eliminating the manual fetch step. My question is: is this an acceptable practice, or could this result in other unforeseen issues?

Answered by Frameworks Engineer in 836915022

The CKSyncEngine relies on the .ifServerRecordUnchanged save policy which states:

The server maintains a change tag for each record automatically. When you fetch a record, that change tag accompanies the rest of the record’s data. If the change tag in your local record matches the change tag of the record on the server, the save operation proceeds normally. If the server record contains a newer change tag, CloudKit doesn’t save the record and reports a CKError.Code.serverRecordChanged error.

This implies that to successfully save an existing record via CKSyncEngine you must return an existing record with it's system fields, see encodeSystemFields(with:). In general it's recommended to save said CKRecord alongside your local database, using it to generate changes that will be saved to the server.

So while your approach will work it's suboptimal and could potentially lead to data loss if incorrectly implemented.

Accepted Answer

The CKSyncEngine relies on the .ifServerRecordUnchanged save policy which states:

The server maintains a change tag for each record automatically. When you fetch a record, that change tag accompanies the rest of the record’s data. If the change tag in your local record matches the change tag of the record on the server, the save operation proceeds normally. If the server record contains a newer change tag, CloudKit doesn’t save the record and reports a CKError.Code.serverRecordChanged error.

This implies that to successfully save an existing record via CKSyncEngine you must return an existing record with it's system fields, see encodeSystemFields(with:). In general it's recommended to save said CKRecord alongside your local database, using it to generate changes that will be saved to the server.

So while your approach will work it's suboptimal and could potentially lead to data loss if incorrectly implemented.

I noticed that after saving a record to the database, a fetchedRecordZoneChanges event will be triggered by CKSyncEngine, returning the modified record that was just saved. Although this seems redundant and inefficient, is the purpose of this to locally persist the newly saved record?

CKSyncEngine save existing CKRecord
 
 
Q