Removing CKRecord is really confusing

So I have a really weird problem, I suppose I either don't understand how CloudKit works under the hood, or I've encountered a bug in CloudKit.

So the problem looks like this:

Initial state of the application:

I have 5 "Package" entries, lets call them A, B, C, D, E.

User action

The user will delete the "Package" E entry and at some later point they will click the refresh button, which will fetch all the current "Package" entries from the cloud.

Problem

When the user clicks the refresh button, the application will basically look at the existing locally saved Batch records and create a CKQuery with a predicate that should retrieve any other records that do not exist locally. The next step basically calls the [database performQuery: inZoneWithID: completeHandler:] method .

The surprise comes when I get results that contain the "Package" E entry that the user previously deleted.

It doesn't sound like me ...

Steps I took to debug:

  • Immediately after deleting the "Package" E record, I created a CKFetchRecordsOperation and tried to fetch the deleted record. The result was as expected: I got "Record not found". I'm cool here.

  • Thinking there might be some latency on the server side, I placed a dispatch_after block and ran the same fetch operation as in the first point, but only after 30 seconds. The result was as expected: I got a "Record not found" error.

  • The same test was performed as in step 2, but with a delay of 100 seconds and ... surprise, the CKFetchRecordsOperation operation returned the packet of the deleted record E. The strange thing is that someday it will still return an error, but sometimes it just will just return the remote object.

And now the really weird part: it doesn't happen with the A, B, C and D entries, the only difference between all of these entries is their names. It doesn't make any sense.

I filled out a bug report and the response I received was as follows:

This is the correct behavior. The requests are eventually matched, so the deletion may not be immediately reflected in the request. Retrieving a deleted record by ID via CKFetchRecordsOperation should return CKErrorUnknownItem immediately.

While this is partially true, it doesn't look like what I see.

code

  • Deleting an E record named DS2000330803AS, check operation CKFetchRecordsOperation returns an error with record not found. Everything is fine here.
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDB = [container privateCloudDatabase]; 

CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName: @"DS2000330803AS"];

CKModifyRecordsOperation *operation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave: nil recordIDsToDelete: @[recordID]];
operation.database = privateDB;

[operation setModifyRecordsCompletionBlock:^(NSArray<CKRecord *> * _Nullable savedRecords,
                                         NSArray<CKRecordID *> * _Nullable deletedRecordIDs,
                                         NSError * _Nullable error) {

    CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[recordID]];
    fetchOperation.database = privateDB;
    [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){
        NSLog(@"Error: %@", error.localizedDescription);
    }];
}];

      

  1. Placing an NSTimer in my VC just to check for deleting an entry, this piece of code will return the deleted entry:
[NSTimer scheduledTimerWithTimeInterval:100 repeats:NO block:^(NSTimer * _Nonnull timer) {

    CKContainer *container = [CKContainer defaultContainer];
    CKDatabase *privateDB = [container privateCloudDatabase];

    CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"DS2000330803AS"];

    CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs: @[recordID]];
    fetchOperation.database = privateDB;
    [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){
        NSLog(@"Error: %@", error.localizedDescription);
    }];

    [privateDB addOperation: fetchOperation];
}];

      

  1. A piece of code that retrieves all existing records by clicking the refresh button which the user can click at any time. I have simplified this code a bit to just expose the problem, basically the executeQuery function returns a DS2000330803AS record and to test my sanity I add a CKFetchRecordsOperation to rewrite the record, which of course returns it without any problem.
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDB = [container privateCloudDatabase];

NSPredicate *predicate = [NSPredicate predicateWithValue: YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Package" predicate:predicate];

[privateDB performQuery:query
     inZoneWithID:nil       completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) {

    [results enumerateObjectsUsingBlock:^(CKRecord * _Nonnull record, NSUInteger idx, BOOL * _Nonnull stop) {

        NSLog(@"Record ID: %@", record.recordID);

        CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs: @[record.recordID]];
        fetchOperation.database = privateDB;
        [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){
            NSLog(@"Error: %@", error.localizedDescription);
        }];

        [privateDB addOperation: fetchOperation];
    }];   
}];

      

Other Notes: I've removed and commented out pretty much everything CloudKit related and the above code is the only one that interacts with CloudKit. I am currently testing one device.

I know CKQuery might have a better NSPredicate, but now I'm trying to figure out why I am having this problem.

Ps When I added the first CloudKit implementation to my application, I tried to keep it as simple as possible, without any fancy sync settings. It worked great for a year, then I started getting reports from my users that they were unable to delete some records during production.

Any hints guys on how I should debug this?

Thank!

+3


source to share


1 answer


I think you are mixing type notation and Name (String of CKRecordID) notation. The name is assigned by CloudKit (usually) and the type is assigned by you. I would bet it was automatically assigned, but I would need to see how the entry was saved. It would be told to see a screenshot of your CloudKit panel.

In the block of code in 1), you are trying to delete the record name of any record using the record type. This is why you get the "Record not found" error 2) Same as you are still using the record type and not the record name 3) Selects the record because the assigned record.recordID is actually being used.



This is my gut in the situation. As for deleting and updating, see my answer on strings to keep the UI and database in sync.

+1


source







All Articles