Embracing CloudKit: Part 3

Posted by Stuart Wheelwright on May 15, 2023 · 11 mins read

Part 3: Adding, Updating and Deleting Records

This is the third in an eight-part series on implementing data sharing in Shopping UK using CloudKit.

Shopping UK is a smart shopping list for UK shoppers. It knows almost every product in the supermarket and will arrange them by aisle. Lists can be shared with family or friends.

Last week, we looked at CloudKit concepts and the design of the Shopping UK schema. Today, we’ll look at how CKRecords are uploaded to iCloud using CloudKit; and the three ways the app can fetch CKRecords.

Adding and Changing Data

To create, modify or delete a CKRecord in iCloud, the app must send a CKModifyRecordsOperation message to CloudKit.

Modifying data using CloudKit

Shopping UK uses CKModifyRecordsOperation to upload new JournalEntry CKRecords to iCloud so they can be fetched by other devices that share the shopping list.

Let’s look at what happens when a user adds two new items to the list.

Step 1. User adds items to the shared list

There are three ways a user can add items to the list:

  1. Start typing and choose a suggestion.
  2. Copy items from another list.
  3. Paste from another app, such as Notes, using the clipboard.

User adds items from the drawer to the “Weekly Shop” list

It doesn’t matter how the user added “milk” and “bread” to their shopping list, the way they are uploaded to iCloud via CloudKit is the same.

Step 2. App saves changes to local journal

Shopping UK maintains a local journal of changes. All changes are first written to this journal before the app sends them to iCloud.

This step is not essential for uploading data to iCloud using CloudKit but, if your app must guarantee changes will be uploaded, it is wise to first store them on the device, even if only temporarily, before uploading the changes to iCloud.

By doing this, we can be sure nothing will be lost if the network is not available. We’ll examine how things can fail in depth in part 8.

The app creates two JournalEntry records to represent the changes

Step 3. App sends CKRecords to iCloud using CloudKit

The app creates a CKModifyRecordsOperation message and attaches the two CKRecords that represent the changes from the local journal.

The app creates a CKModifyRecordsOperation and sends it to iCloud using CloudKit

Note: The app can set a Quality of Service (QoS) value for the CKModifyRecordsOperation. A lower level QoS is used for operations that are not time critical. iOS may delay these operations when the device is in low power mode or low on battery.

Shopping UK uses the User-Initiated level for sending and fetching JournalEntry records. This provides nearly instantaneous messaging (a few seconds or less).

Step 4. CloudKit sends response back to app

CloudKit adds the CKRecords to the CKDatabase and returns three response messages.

iCloud sends back three messages: one for each record and a final message when the overall operation has completed

All CloudKit operations are asynchronous and typically follow this pattern:

  • The app sends the initial message to CloudKit
  • CloudKit will return a response message for each completed unit (i.e. two responses, one for each CKRecord)
  • CloudKit will return another response message when the entire operation is complete.

The happy path is shown here — when everything goes well — but, due to the nature of networks and remote systems, things can go wrong. In part 8, we will revisit the error scenarios and how Shopping UK handles each one.

Fetching Data

We looked at uploading new CKRecords to iCloud, but how is data fetched?

There are three options:

Type Operation to use
Fetch By Name CKFetchRecordsOperation
Fetch By Query CKQueryOperation
Fetch Changes CKFetchDatabaseChangesOperation

Fetching data using CloudKit

Shopping UK uses all three techniques in different places.

Fetch By Name is used to:

  1. Retrieve a CKShare record to allow the user to manage the shared list (more on this in part 4 and part 7)
  2. Retrieve the current ShoppingList record for compression (more on this in part 7)

Fetch By Query is used to fetch JournalEntry records eligible for compression (more on this in part 7).

Fetch Changes is the most interesting of the three. It uses a CKServerChangeToken to identify each version in the database’s history, which means the app can request just the records that changed since its last request. This is much more efficient than fetching everything every time, and it is more reliable than creating a custom mechanism for tracking changes.

For my first attempt at implementing sharing using CloudKit, before I understood how Fetch Changes worked, I tried to build my own change tracking mechanism using timestamps. I spent a long time on this dead-end, but it didn’t work well due to the precision of timestamps, and tiny differences in the clock setting of each device

Shopping UK uses Fetch Changes to retrieve changes made by other devices participating in a shared list.

Step 1. App sends a Database Changes request to CloudKit

Assume two CKRecords already exist in iCloud and this is the first time the white iPhone is requesting changes.

Since this is the first time data will be fetched, the device won’t have a Database CKServerChangeToken so it will send null for the token’s value. This instructs CloudKit to return all changes ever made.

App requests any changes to the database

Step 2. CloudKit returns changed Record Zones

CloudKit will return three response messages:

  1. A Change Token Updated response containing the updated Database CKServerChangeToken (e.g. ABC123)
  2. A single Changed Record Zone response (because both CKRecords are contained within a single CKRecordZone: abcd1234-…)
  3. A final Changes Completed response

The app receives a response for each zone that has changed

If multiple zones had changed, there would be a Changed Record Zone response for each zone that contained a change.

If any zones had been deleted, an additional Deleted Record Zone response would have been returned too.

Step 3. App saves database change token

The database change token is stored so it can be used next time CKFetchDatabaseChangesOperation is sent.

The app stores the database change token

Step 4. App requests changes in each zone

Now we have a list of CKRecordZones that changed, but we don’t yet know which CKRecords. To find these, the app issues a CKFetchRecordZoneChangesOperation.

Since this is the first time this has been issued, the app doesn’t yet have a Zone CKServerChangeToken so it sends null.

The app sends a CKFetchRecordZoneChangesOperation

Note: the Database CKServerChangeToken returned in step 2 is not the same as the Zone CKServerChangeToken

Step 5. CloudKit responds with changed records

CloudKit will send a separate Record Changed for every record added or modified since the Zone CKServerChangeToken. Because this is the first time this operation has been used, and the token was set to null, all records (milk and bread) will be returned.

A single Fetch Completed response will be sent at the end.

CloudKit sends the changed records back to the app

If a record had been deleted since the last fetch, a Record Id Deleted response would also be received by the app.

When the fetch operation is complete, a Fetch Completed response will be received by the app. This will contain the Zone CKServerChangeToken.

Step 6. App updates its local list and stores the Zone Change Token

The app uses the received CKRecords to update its local representation of the list, and it updates user interface with the new items.

The app stores the CKServerChangeToken for zone “abcd1234-…” on the device. This will be used next time CKFetchRecordZoneChangesOperation is used to fetch changes for the same zone.

The app stores the zone change token

CloudKit’s Fetch Changes mechanism provides an efficient way of synchronising the app’s local cache of the iCloud data. Instead of fetching everything every time, only the recent changes need to be fetched.

In part 6 we’ll look at how the Fetch Changes request can be triggered: when the app receives a remote notification from a CloudKit Subscription or when the user explicitly triggers the list synchronisation.

And, in part 7, we’ll look at situations when the app must fetch everything, such as when the user signs into a new iCloud account.

Next week, we’ll see how sharing works, and how to share a list for the first time.

If you get a chance, please try Shopping UK and let me know what you think at @wheelies