Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CloudKit: Delete local objects based on CKQueryNotification

I'm building an app that heavily relies on CloudKit for data synchronization. Each time the app launches, it catches up with all changes that have been made on the server using a CKFetchNotificationChangesOperation. This successfully returns all objects that have been created and/or modified, but I am now at the stage that I also want my app to delete records based on these messages.

In my app, each object that I have stored in CoreData also carries the recordID of the online representation of that object. This way I can easily pick up objects I need to modify.

This seems hard when deleting objects, as CloudKit only returns recordID's for these objects, and does not provide a recordType that I can use to know what object I am looking for in my CoreData Database.

Question

How does one correctly handle CloudKit 'deleted' notifications in a case with multiple record types?

like image 718
Joris416 Avatar asked Jul 31 '17 15:07

Joris416


2 Answers

If CloudKit doesn't give you any indication of the type of record that was deleted, it's kind of a pain to deal with. You can't delete objects in Core Data without knowing the entity type, so if CloudKit doesn't give you any clues then you need to check every entity that could have the recordId.

The delete process is the same as usual with Core Data. Do a fetch request with a predicate of something like `recordId = %@" to find a matching object. If you find one, delete it. Except that you have to repeat this for each potential entity.

One approach that might help is to store the recordId in a new, separate entity. Create a new entity called something obvious like CKRecordInfo and keep the recordId there. Every other entity that has CloudKit info would have a one-to-one relationship to this entity. With this setup you'd fetch an instance of the new CKRecordInfo entity, and delete whichever object it's related to.

At the same time though-- I haven't used CloudKit, and it's more than a little surprising that it would give you just the recordId with no information about the record type. Getting the info from the notification would be ideal, if it's possible.

like image 135
Tom Harrington Avatar answered Nov 05 '22 19:11

Tom Harrington


Based on your clarification in the comments, I suggest configuring the .recordFields dictionary when you create the subscription. You can pass a limited amount of info in this dictionary, such as the record type. When you receive the deletion notification, you can unpack the recordFields from the notification object.

You can find more info in Apple's docs at https://developer.apple.com/documentation/cloudkit/ckquerynotification/1428114-recordfields

Update

Here's how I do it. I use objective-C, so you'll have to sort out the SWIFT syntax. But the steps are:

Create an array of records I want sent in the notif

Create a subscription

Create a notificationInfo object

Add my desired keys array to the notificationInfo object

Create the sub using a CKModifySubscriptionsOperation

NSArray *desiredKeys = @[fieldname1, fieldname1, fieldname1];
CKQuerySubscription *subscription = [[CKQuerySubscription alloc] initWithRecordType:recordName
                                                                          predicate:searchConditions
                                                                     subscriptionID:subscriptionID
                                                                            options:fireOn];

CKNotificationInfo *notificationInfo = [CKNotificationInfo new];
notificationInfo.shouldBadge = shouldBadge;
notificationInfo.desiredKeys = desiredKeys;

subscription.notificationInfo = notificationInfo;

CKModifySubscriptionsOperation *subOp = [[CKModifySubscriptionsOperation alloc] initWithSubscriptionsToSave:subsToCreate subscriptionIDsToDelete:subsToDelete];
subOp.modifySubscriptionsCompletionBlock = ^(NSArray<CKSubscription *> *savedSubscriptions,
                                             NSArray<NSString *> *deletedSubscriptionIDs,
                                             NSError *operationError)
{
   //do whatever
}

subOp.database = database;      //set to either public or private DB
[myQueue addOperation:subOp];

When you receive a notification, you just pull the objects back out of the notificationInfo:

    NSString *value1 = [queryNotification.recordFields objectForKey:fieldname1];

If it won't let you actually add the recordType, then you may have to create a custom field with some indicator of the record type, and then pass that as I described above, or fetch the record in question by using the recordID you received in the notification.

like image 41
Thunk Avatar answered Nov 05 '22 19:11

Thunk