(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 CKRecord
s 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.