Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CloudKit CKModifyRecordsOperation gives me a "Protection data didn't match"

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">
}>
like image 614
Jonathan Avatar asked Sep 12 '14 20:09

Jonathan


2 Answers

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).

like image 143
Gary Avatar answered Sep 29 '22 13:09

Gary


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.

like image 33
Dave Teare Avatar answered Sep 29 '22 11:09

Dave Teare