Reconcile deleted CKRecord to CoreData NSManagedObject

94 Views Asked by At

I have created CKQuerySubscriptions to monitor remote insertion, modification and deletion of CKRecords. For inserted and modified records this works well because I can query CloudKit for the affected CKRecord, get the associated NSManagedObject and then handle the insertion and modification from there.

For deleted CKRecords, this is a problem because by the time the notification has been fired, the CKRecord has already been removed from CloudKit. This means the fetch request to get the now deleted CKRecord fails, so I have no way to know which NSManagedObject was associated with the deleted CKRecord.

I don't know if I'm going about this all the wrong way and if there is an easier way to handle all of this!

2

There are 2 best solutions below

0
On

This works, but it feels a bit clunky. There must be an easier way! But if not, if this code is useful to anybody else, feel free to comment if you want the code used in any of the helper methods not shown (e.g. in the +[CoreDataFunctions fetchRecordsForEntityType: withCloudIDs: completion:] method);

//Array to hold all cloudIDs of existing NSManagedObject instances
NSMutableArray *cloudIDs = [NSMutableArray array];

//Populate cloudIDs array with the IDs of the existing NSManagedObject instances
for (NSManagedObject *item in self.items) {
    NSUUID *cloudID = [item valueForKey:@"cloudID"];
    [cloudIDs addObject:cloudID];
}

//Array to hold remaining NSManagedObject instances (i.e. the ones which were not deleted)
NSMutableArray *remainingItems = [NSMutableArray array];

//Fetch all remaining CKRecords (i.e. the ones which were not deleted
[CoreDataFunctions fetchRecordsForEntityType:[self managedObjectMonitoringClass] withCloudIDs:cloudIDs completion:^(NSArray<CKRecord *> *results) {
    //For each local NSManagedObject instance
    for (NSManagedObject *item in self.items) {
        //The cloudID for the local NSManagedObject instance
        NSString *localCloudID = [[item valueForKey:@"cloudID"] UUIDString];

        //For each CKRecord in CloudKit
        for (CKRecord *record in results) {
            //The cloudID for the remote CKRecord object
            NSString *remoteCloudID = [record valueForKey:@"CD_cloudID"];

            //If the local and remote cloudIDs match, the local NSManagedObject entity represents a CKRecord which still exists in CloudKit
            //Add the NSManagedObject entity to the remainingItems array
            if ([remoteCloudID isEqualToString:localCloudID]) {
                [remainingItems addObject:item];
                break;
            }
        }
    }

    //Array to hold NSIndexPath objects to be removed from the collectionView
    NSMutableArray *indexPaths = [NSMutableArray array];

    //For each NSManagedObject stored locally
    for (NSManagedObject *item in self.items) {
        //If the remainingItems array does not contain this NSManagedObject, it has been deleted from CloudKit
        //Create and indexPath for this item and add it to the array
        if (![remainingItems containsObject:item]) {
            NSInteger index = [self.items indexOfObject:item];
            [indexPaths addObject:[NSIndexPath indexPathForItem:index inSection:0]];
        }
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [[self TBcollectionView] performBatchUpdates:^{
            //Set the local items array to whatever is remaining in CloudKit
            self.items = remainingItems;
            //Delete the indexPaths for the items which were deleted
            [[self TBcollectionView] deleteItemsAtIndexPaths:indexPaths];
        } completion:nil];
    });
}];
0
On

I use subscriptions with remote notifications and CKFetchRecordZoneChangesOperation.

If the "application(didReceiveRemoteNotification:)" method is called, if build and fire a "CKFetchRecordZoneChangesOperation".

There are several completion handlers. One is for the updated records (add / modified) and there is a special one for deleted records.

This handler is called "recordWithIDWasDeletedBlock" and is called for each single record, which has been deleted, providing the recordID of the deleted record. With this information you should be able to process what you need.