I'm trying to upload some record changes with CloudKit. I'm truing to use a CKModifyRecordsOperation to do a batch upload of records that have changed on the device. The records are all in a custom zone
For some reason the operation keeps coming back with an error telling me ""Protection data didn't match"
Here's the code:
- (void)updloadLocalChangesWithCompletionBlock:(void (^)(NSError *error))completionBlock
{
// Initialize the data
NSArray *localChanges = self.localChanges;
NSArray *localDeletions = self.localDeletions;
// Initialize the database and modify records operation
CKDatabase *database = [CKContainer defaultContainer].privateCloudDatabase;
CKModifyRecordsOperation *modifyRecordsOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:localChanges recordIDsToDelete:localDeletions];
modifyRecordsOperation.savePolicy = CKRecordSaveAllKeys;
NSLog(@"CLOUDKIT Changes Uploading: %d", localChanges.count);
// Add the completion block
modifyRecordsOperation.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *error) {
if (error) {
NSLog(@"[%@] Error pushing local data: %@", self.class, error);
}
[self.localChanges removeObjectsInArray:savedRecords];
[self.localDeletions removeObjectsInArray:deletedRecordIDs];
completionBlock(error);
};
// Start the operation
[database addOperation:modifyRecordsOperation];
}
Here is the error it's giving me:
[CloudKitSyncManager] Error Uploading Changes: <CKError 0x156654a0: "Partial Failure" (2/1011); "Failed to modify some records"; partial errors: {
default-00001:(ZoneName:UserRecordID) = <CKError 0x1550b5a0: "Server Record Changed" (14/2037); "Error saving record <CKRecordID: 0x18080430; default-00001:(ZoneName:UserRecordID)> to server: Protection data didn't match">
}>
If you use the savePolicy
of CKRecordSaveIfServerRecordUnchanged
, it is necessary to pre-fetch a remote CKRecord
(and update that particular instance) before CKModifyRecordsOperation
. If you allocate a "local" CKRecord
and up-reference it through CKRecordID
with a matching initWithRecordName:
, CloudKit cannot compare a remote change-tag to a (missing) local change-tag, so it fails with CKErrorServerRecordChanged
and the somewhat vague error message: "protection data didn't match".
However, if a remote CKRecord
does not exist at all, surely the server record has not changed and the save operation can proceed. This behavior is the basis for the "Save If Not Exists" use case (SQL: INSERT over a PRIMARY KEY). If you allocate a "local" CKRecord
and use the savePolicy
of CKRecordSaveIfServerRecordUnchanged
, it will INSERT, but never UPDATE.
If you use the savePolicy
of CKRecordSaveAllKeys
and the CKRecordZoneID
ownerName
of CKOwnerDefaultName
, you should be able to save (SQL: UPDATE, with INSERT if necessary) through a "locally" allocated CKRecord
, saving a (pre-fetch) trip over the network.
The "Save If Exists" use case (SQL: UPDATE over a PRIMARY KEY) probably cannot be done without a (verification) trip over the network.
A catch: it is not possible to roll both a CKRecordSaveIfServerRecordUnchanged
INSERT and a CKRecordSaveAllKeys
UPDATE into one atomic transaction, since CloudKit transactions span only a single CKModifyRecordsOperation
, across multiple CKRecord
/CKRecordID
instances. Philosophically speaking, a "transaction" should be able to span multiple "operations" (SQL: queries), not just multiple "records" (SQL: rows).
What exactly is in your self.localChanges
array? CKRecord
I assume, but are the modified records you're uploading based on the same CKRecord objects you downloaded from CloudKit?
I had a similar (albeit not exact) error message when I tried uploading a freshly allocated CKRecord with my local changes and expected it to overwrite the server's copy. I fixed it by downloading the remote copy of my item, applying updates to the CKRecord instance given to me by CloudKit, and then uploading it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With