Scorebook Sync Log 03 - Schema Design

Building a schema on CloudKit is pretty straightforward. You get a CKRecord object and treat it like a dictionary. There are a few data types they can take (NSString, NSNumber, NSData to name a few) and it’s a key/value pair to get the data on the record. Once you have a record created, you save it to the database and your RecordType (i.e. a database table) is created. Simple. But there is a trick associated with related data.

One of the data types you can attach is a CKReference. This is how you glue 2 records together. CloudKit supports 1:Many relationships (not many:many though). The way you do the relating is through the child record, not the parent. This tripped me up a little bit because with my background in FileMaker I would typically go parent to child (like with a portal object on a layout).

Let me back up for a moment. I made a design decision early on to isolate my networking files as much as possible. It could be a hedge or just wanting to have all the syncing code in one place, it jut felt right. I put a new folder in my Model folder called Sync and in it I have categories on all my model objects called <objectName>+CloudKit.h/m. I want each entity to know how to turn itself into a CKRecord and how to turn a CKRecord into itself. That feels like the right way to go.

Inside each category I have a method called -cloudKitRecord that returns a CKRecord. Here’s a basic implementation:

- (CKRecord *)cloudKitRecord
{
    CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:self.ckRecordId];
    CKRecord *personRecord = [[CKRecord alloc] initWithRecordType:[SBPerson entityName]
                                                         recordID:recordID];
    
    personRecord[SBPersonFirstNameKEY] = self.firstName;
    personRecord[SBPersonLastNameKEY] = self.lastName;
    personRecord[SBPersonEmailAddressKEY] = self.emailAddress;
    
    if (self.imageURL) {
        CKAsset *imageAsset = [[CKAsset alloc] initWithFileURL:[self urlForImage]];
        personRecord[SBPersonAvatarKEY] = imageAsset;
    }
    
    return personRecord;
}

A few things to note here:

  • I’m overriding -awakeFromInsert on all of my managed objects to create a -ckRecordId string property (Just a random UUID) that I can use as my CloudKit record ID. This is the unique identifier across the system. I started out with something much more complicated but landed here for now.
  • I have used Accessorizer (MAS link) to create string constants for all of my managed object properties. See this talk from Paul Gorake for more Core Data amazingness.
  • You can see my new CKAsset code at work here. I’m creating a new image asset with the URL to the image on disk, then you just use the asset as a property on the CKRecord. CloudKit handles the rest, which is really nice.

On the flipside, I have methods in the category that will take a CKRecord and create the Core Data managed object out of it. I haven’t gotten to the downstream sync just yet (I just ran into the need for CKRecordZones that I’ll probably write about next). Once I start the downstream I’ll put up a post about it too.

I’m going to come back to the CKReference stuff in my next post, since this one got a bit too long. Stay tuned.