CKSyncEngine.RecordZoneChangeBatch and the CKSyncEngineDelegate protocol

I'm having some trouble with the following function from the CKSyncEngineDelegate protocol.

func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext, 
    syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {

The sample code from the documentation is

func nextRecordZoneChangeBatch(
_ context: CKSyncEngine.SendChangesContext, 
syncEngine: CKSyncEngine
) async -> CKSyncEngine.RecordZoneChangeBatch? {

// Get the pending record changes and filter by the context's scope.
let pendingChanges = syncEngine.state.pendingRecordZoneChanges
    .filter { context.options.zoneIDs.contains($0) }


// Return a change batch that contains the corresponding materialized records.
return await CKSyncEngine.RecordZoneChangeBatch(
    pendingChanges: pendingChanges) { self.recordFor(id: $0) }
}

init?(pendingChanges: [CKSyncEngine.PendingRecordZoneChange], recordProvider: (CKRecord.ID) -> (CKRecord?)) works fine for the sample app which only has one record type, but it seems incredible inefficient for my app which has a dozen different record types. The recordProvider gives you a CKRecord.ID, but not the CKRecord.RecordType. Searching each record type for a matching ID seems very inefficient.

Doesn't the CKSyncEngine.PendingRecordZoneChange contain an array of CKRecords, not just CKRecord.IDs? According to the documentation CKSyncEngine.RecordZoneChangeBatch has a recordsToSave property, but Xcode reports 'CKSyncEngine.PendingRecordZoneChange' has no member 'recordsToSave'

I'm looking for someway to get the CKRecords from syncEngine.state.pendingRecordZoneChanges.

No, CKSyncEngine.PendingRecordZoneChange is used to communicate a save or delete action for a record ID, not a full record. You see an example of that in the sample code that I assume you're referring to here.

It's possible that the local storage backing your CKRecords can change in the time between when you tell CKSyncEngine about pending changes and the time it's ready to actually save those changes to the server via nextRecordZoneChangeBatch (e.g. the user took a second action quickly after the first that led to a second state mutation), therefore CKSyncEngine only communicates via IDs in the meantime. This has an important side effect of reducing memory usage as well.

Notice how the sample code persists a unique identifier on-device for its Contact model and uses that same identifier as the recordName in its recordIDs, allowing it to quickly access the on-device version here. For simplicity, the sample code doesn't show using a database on-device to store this, but presumably you can configure your model's id field to be indexed, allowing for efficient lookup.

"presumably you can configure your model's id field to be indexed, allowing for efficient lookup." I do have the id indexed, but now I have to search on up to 12 tables when I only have recordID and not recordType.

I have the exact same issue. I haven't tried it yet, but I think a lookup over persisted CKRecord encodeSystemFields should work. Since you need to persist the system fields anyway, there is no extra work to do. The only difference would be that you would have to persist the system fields before telling CKSyncEngine what to do.

CKSyncEngine.RecordZoneChangeBatch and the CKSyncEngineDelegate protocol
 
 
Q