(Thanks to David Hoang for the encouragement to post this)
I thought I was close to being done, and then I remembered that I havenāt implemented responding to deletions from CloudKit locally. Hereās how the process works:
- Delete the object from Core Data & save the context
- Respond to the context did save notification and send CloudKit
CKRecordID objects representing the deleted record to CloudKit
- CloudKit will send a notification to all subscribed devices letting them know of the deletion, with the corresponding
CKRecordID object.
- Figure out what entity that object belongs to, and delete the instance from the database
- Save the updated database.
Pretty easy, right? Except, consider this: there isnāt a way to know the recordType of the CKRecordID coming down. The recordType is the database equivalent of a table, and itās a property on CKRecord, not CKRecordID. When Iām creating CKRecords from my managed objects, I set the recordType property to a string representing my entity. That keeps things clean (i.e. my SBGame instances would all have record types of SBGame).
I havenāt talked about uniquing objects in CloudKit yet, so letās go back to the beginning there. When you create a CKRecord you can specify itās unique ID (made up of a recordName property which is a string, and a zone if itās going to a custom zone). Those 2 factors combine with the CKRecordās recordType property to create a primary key. Good database practice so far. You can also choose not to specify a recordID at the CKRecordās creation, and it will be created when saved to the server.
For ease, I have decided to create a unique ID as a UUID string that is set on -awakeFromInsert on all of my entities. That way I always know what their unique ID will be, and I donāt have to worry about saving them back to the database when they are saved to the cloud.
The first step to determining what record type the CKRecordID Iām responsible for deleting, I made a change to each entityās -recordName property, so that it includes the entity name as the first part of the record name. Then itās followed by a delimiter (Iām using ā|~|ā) and then the UUID. Hereās how that now looks:
- (void)awakeFromInsert
{
[super awakeFromInsert];
self.ckRecordName = [NSString stringWithFormat:@"SBGame|~|%@", [[NSUUID UUID] UUIDString]];
}
A sample recordName for an SBGame instance would be āSBGame||386c1919-5f25-4be2-975f-5b34506c51dbā, with || being the delimiter between the entity name and the UUID. A bit hacky, but it works.
On the downloader I explode the string into an array using he same delimiter and grab the first object, which will be āSBGameā. Once I have the entity and the recordName to search for, I can put this in a fetch request to grab the object that needs deletion. Hereās what that looks like:
NSString *recordName = recordID.recordName;
NSString *recordType = [[recordName componentsSeparatedByString:@"|~|"] firstObject];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ckRecordName = %@", recordName];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:recordType];
fetchRequest.predicate = predicate;
NSArray *foundObjects = [backgroundContext executeFetchRequest:fetchRequest error:nil];
id foundObject = [foundObjects firstObject];
if (foundObject != nil) {
[backgroundContext deleteObject:foundObject];
}
In my limited initial testing this is working well so far, and itās a simple solution to the problem.