As I Learn CloudKit Syncing - Part 5: Tracking Game Changes
This is part 5 in a series of posts talking through how I built CloudKit syncing into Qiktionary
In the last installment of this series I discussed how I was tracking newly created games.
Tracking changes to a game that has already been synced with CloudKit has to be approached in a different way.
The first approach I thought about using was based on time. I would track the “last updated” times of all the games and also track the last sync time. Then I could just query for all games with a more recent “last updated” value.
I decided against that approach for a couple of reasons. I was worried I wouldn’t be able to correctly track the last synced date when things went wrong with a sync, thus possibly letting some game updates slip through the cracks. And I also wanted to be a good CloudKit citizen and only send values that had actually changed since the last sync, and not the entire game every time there was a change.
The approach I’m using
I decided to create a table called GameChange that stores the
gameID and a set of
changedFields that correspond to column names on the Game table. When a game is updated I create a GameChange record with the set of fields that have changed on the game. If there is already a GameChange record for that game, I update the GameChange’s
changedFields value with any new fields that have changed on that game.
This makes it really easy to know what games have changed, all I have to do is
SELECT * FROM GameChange. Then, after those changes have been sent to CloudKit using the
CKModifyRecordsOperation1, I can just delete the GameChange record.
Don’t track changes that don’t need to be synced
It took me a bit to really figure this out but it may seem pretty obvious to you: only track changes to a game that need to be sent to CloudKit. For example, I ignore changes when:
- Updating the
- Updating a game with new data that was fetched from CloudKit.
I do this by having two different “save” methods on my Game class,
save method is inherited from
FCModel, and it looks a bit like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Note the call to
changedFieldNamesWithoutRemoteColumns which returns an array of field names that have changed on the game record, filtering out the
remoteRecordID column that we don’t need to sync with iCloud.
saveWithoutTrackingChanges just sets the stopSaveChangeGeneration flag around a call to
1 2 3 4 5 6 7 8 9 10
The other important piece here is the call to
[GameChange saveChangeForGame:self withChangedFields:changedFieldNames], which is implemented like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Bonus: tracking game deletions
I take a similar approach to tracking when a local game is deleted. I have a GameDeletion table that stores the
CKRecordID of any game that has been deleted (and has already been synced to iCloud). I don’t keep the local Game record in the database either (with something like a
deleted BOOL to mark it as deleted). The Game record is really deleted from the database, along with it’s corresponding RemoteRecord entry.
When I’m setting up my
CKModifyRecordsOperation, all I have to do is
SELECT * FROM GameDeletion. Then after the operation succeeds, I can delete the GameDeletion record.
If you recall the syncing recipe is as follows:
- Track local changes
- Send changes to the server
- Resolve conflicts
- Fetch server changes with
- Apply server changes
- Save server change token
Now that I’ve tracked all the local changes I need to track, it’ll finally be time to start sending those changes to the server. That I will start covering in my next post.
I’ll eventually get to how I’m using the
CKModifyRecordsOperationto send all these local changes to CloudKit.↩