CKSyncEngine with dependent CKRecords

Hi,

when using CKSynEgine it is the responsibility of the app to implement CKSyncEngineDelegate. One of the methods of CKSyncEngineDelegate is nextFetchChangesOptions. The implementation of this method should return a batch of CKRecords so that CKSyncEngine can do the syncing whenever it thinks it should sync. A simple implementation might look like this:

func nextRecordZoneChangeBatch(
        _ context: CKSyncEngine.SendChangesContext,
        syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch?{
    await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: syncEngine.state.pendingRecordZoneChanges) { recordID in
        // here we should fetch to local representation of the value and map it to a CKRecord
    }
}

The problem I am having is as follows: If the CKRecords I am returning in a batch have dependencies between each other (using CKRecord.Reference or the parent property) but are not part of the same batch, the operation could fail. And as far as I understand, there is no way to prevent this situation because:

A: The batch I can return is limited in size. If the number of CKRecords is too large, I have to split them into multiple batches.

B: Splitting them is arbitrary, since I only have the recordID at this point, and there is no way to know about the dependencies between them just by looking at the recordID.

So basically my question is: how should the implementation of nextRecordZoneChangeBatch look like to handle dependencies between CKRecords?

Answered by DTS Engineer in 801723022

Did you really see that kind of error? If yes, would you mind to share the details about how you trigger the error?

I'd assume that, when triggering nextRecordZoneChangeBatch(_:syncEngine:), CKSyncEngine will ensure the records that have dependencies are included in context.options.scope (CKSyncEngine.SendChangesOptions.Scope), if you put the records together when adding a pending change.

For example, if you relate two records with CKReference, you'd save both records when adding the pending change:

let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [theSourceRecord, theTargetRecord].map { .saveRecord($0.recordID) }
syncEngine.state.add(pendingRecordZoneChanges: pendingSaves)

If you did add both records to the pending change, but one of them is excluded in the scope, I’d suggest that you file a feedback report (http://developer.apple.com/bug-reporting/) to see what the CloudKit folks have to say – If you do so, please share your report ID here.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Did you really see that kind of error? If yes, would you mind to share the details about how you trigger the error?

I'd assume that, when triggering nextRecordZoneChangeBatch(_:syncEngine:), CKSyncEngine will ensure the records that have dependencies are included in context.options.scope (CKSyncEngine.SendChangesOptions.Scope), if you put the records together when adding a pending change.

For example, if you relate two records with CKReference, you'd save both records when adding the pending change:

let pendingSaves: [CKSyncEngine.PendingRecordZoneChange] = [theSourceRecord, theTargetRecord].map { .saveRecord($0.recordID) }
syncEngine.state.add(pendingRecordZoneChanges: pendingSaves)

If you did add both records to the pending change, but one of them is excluded in the scope, I’d suggest that you file a feedback report (http://developer.apple.com/bug-reporting/) to see what the CloudKit folks have to say – If you do so, please share your report ID here.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

I have not implemented it yet, but I think my concern is a logical assumption according to the way CKSyncEngine works.

I'd assume that, when triggering nextRecordZoneChangeBatch(_:syncEngine:), CKSyncEngine will ensure the records that have dependencies are included in context.options.scope (CKSyncEngine.SendChangesOptions.Scope), if you put the records together when adding a pending change.

I have been unable to find any information regarding how CKSyncEngine handles this. Could you please advise if there is a way to be sure about that, or if we have to guess and see if it fails in edge cases?

Greetings, Johannes

await CKSyncEngine.RecordZoneChangeBatch( pendingChanges: syncEngine.state.pendingRecordZoneChanges ) { recordID in // here we should fetch to local representation of the value and map it to a CKRecord } ...

B: Splitting them is arbitrary, since I only have the recordID at this point, and there is no way to know about the dependencies between them just by looking at the recordID.

OK, in this context, CKSyncEngine will iterate over the pending changes in order and add them to the batch until it reaches the max batch size, which is 250 records.

That being said, it is your responsibility to make sure that the records having dependency are in the first 250. You create the pending saves, and so I'd think that you are able to group the relevant changes together with your own data structure.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Ok, so the order of the added pending changes is guaranteed not to be changed by CKSyncEngine, right? Is there a way to know the batch size before the batch is built? I know that CloutKit will tell you if you try to save a batch that is too large, but that happens async in a different context, so it is quite difficult to map that error to the batch afterwards.

CKSyncEngine with dependent CKRecords
 
 
Q