Scorebook Sync Log 07 – Responding to Deletions
(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.
(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.