Even by using the right CKServerChangeToken, I was encountering this error because I didn't control the case where the array changedZoneIDs has 0 elements.
Strictly speaking, you need two separate CKServerChangeToken to access the database changes and the zoneChanges (let's say databaseChangeToken and zoneChangeToken). You can create them as Bartłomiej Semańczyk explains
-use recordZoneWithIDChangedBlock to get the zoneID of the changed zones
-use fetchDatabaseChangesCompletionBlock to save the databaseChangeToken and to call the CKFetchRecordZoneChangesOperation where you use the zoneChangeToken,
-each zone has its own changeToken so I ended up with this solution:
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: databaseChangeToken)
var changedZoneIDs: [CKRecordZoneID] = []
operation.recordZoneWithIDChangedBlock = { (zoneID) in
changedZoneIDs.append(zoneID)
}
operation.fetchDatabaseChangesCompletionBlock = { (databaseToken, moreComing, error) in
{
//manage moreComing
//save databaseToken
//manage error
if changedZoneIDs.count > 0
{
var optionsByRecordZoneID = [CKRecordZoneID: CKFetchRecordZoneChangesOptions]()
for zoneID in changedZoneIDs {
let changeTokenData = UserDefaults.standard.value(forKey: "\(zoneID.zoneName) zoneChangeToken") as? Data // Read change token from disk
var zoneChangeToken:CKServerChangeToken?
if (changeTokenData != nil){
zoneChangeToken = NSKeyedUnarchiver.unarchiveObject(with: changeTokenData!)as! CKServerChangeToken?
}
let options = CKFetchRecordZoneChangesOptions()
options.previousServerChangeToken = zoneChangeToken
optionsByRecordZoneID[zoneID] = options
}
let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: changedZoneIDs, optionsByRecordZoneID: optionsByRecordZoneID)
// ...
}