Working with CloudKit Records

Marcus Smith
Frozen Fire Studios
5 min readFeb 2, 2016

--

CKRecord is the class used to manage data in CloudKit, and the main object that you will be working with. Before getting into the specifics of CKRecord, it would help to have an idea of the bigger picture of CloudKit’s overall structure.

CKContainer

A CKContainer is where all CloudKit data for an app is kept. Each app can have only one CKContainer, although apps can be set up to access each others’ containers.

CKDatabase

Each CKContainer has one public CKDatabase for the entire app and a private CKDatabase for each user of the app. Data in the public database is accessible to all users of the app, even users who do not have an iCloud account are able to read its data (but only users with iCloud can write data to this public database). Users with active iCloud accounts can access their private database, and no one else's.

CloudKit is free for developers up to certain limits, which scale with the number of active users that you have, which is nice. Only storage in the Public database counts against these limits, whereas storage in users’ private databases is managed by those users.

CKDatabases are where CKRecord objects are stored, leaving the overall structure looking something like this:

http://www.techotopia.com/index.php/An_Introduction_to_CloudKit_Data_Storage_on_iOS_8

CKRecord

CKRecord as a class works mostly like a dictionary that only accepts certain data types:

https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKRecord_class/index.html

CKRecords also have a few metadata properties

  • recordType: The app-defined string that identifies the type of the record. You define the record types that your app supports and use them to distinguish between records with different types of data.
  • recordID: A CKRecordID object that is used to identify specific records. Record IDs are associated with a CKZone (a smaller division within a CKDatabase) and a recordName string that is unique in that zone.
  • Dates for creation and modification
  • User Record IDs for creation and modification
  • recordChangeTag: A string that can be used to check if a record matches the current version on the iCloud server

Creating Records

Creating a record programmatically is straight forward, you just need a recordType string to init one.

let record = CKRecord(recordType: "Dinosaur")

And like a dictionary, you can use subscripts to assign values to keys within the record, as long as the values are one of the data types that CKRecord accepts. Note that arrays of any of these data types are also acceptable.

record[“name”] = “T-Rex”
record[“numberOfLegs”] = 2
record[“diet”] = [“smaller dinosaurs”, “humans”]

CKContainers have two different environments: Development and Production. If you are in the Development environment, the default for Xcode builds, you can create new recordTypes and record keys on the fly. When the record is saved CloudKit can infer the new data types and update your database schema accordingly. Apple calls this feature just-in-time schema. New record types and fields cannot be created programmatically in the Production environment, but must be deployed from the Development environment in the iCloud dashboard for the app. Once a database schema is deployed to Production, record types and fields can no longer be deleted or renamed, only new types and fields can be created.

Saving Records

CloudKit has a “Convenience API” which are closure-based calls that can be made from a CKContainer or CKDatabase (depending on what the method is). Saving, retrieving and deleting calls are all made from a CKDatabase. To get a CKDatabase, you first need a CKContainer object, which can be gotten in different ways depending on if you used the default container or a custom one.

let defaultContainer = CKContainer.defaultContainer()
let customContainer = CKContainer(identifier: "iCloud.com.frozenfirestudios.CloudKitDemo")

From here you can access both the container’s public database, or the active user’s private database.

let publicDB = defaultContainer.publicCloudDatabase
let privateDB = defaultContainer.privateCloudDatabase

Once you have a database object it is easy to save a record:

publicDB.saveRecord(record) { (record, error) -> Void in
guard let record = record else {
print("Error saving record: ", error)
return
}
print("Successfully saved record: ", record)
}

When creating a record for the first time programmatically it will not have a recordID. The record returned in the saveRecord completion block will be the same as the record that was saved, but with its new recordID added.

Deleting Records

It is also easy to delete records, all you need is the database and the recordID of the record that you want to delete:

let recordID = record.recordID
publicDB.deleteRecordWithID(recordID) { (recordID, error) -> Void in
guard let recordID = recordID else {
print(“Error deleting record: ”, error)
return
}
print("Successfully deleted record: ",
recordID.recordName)
}

Retrieving Records

There are two different ways to retrieve records using the convenience API. If you have the recordID for the record, you can just fetch the record directly.

publicDB.fetchRecordWithID(recordID) { (record, error) -> Void in
guard let record = record else {
print("Error fetching record: ", error)
return
}
print("Successfully fetched record: ", record)
}

If you don’t know the recordID for the record you want, or you want to fetch a group of records that match a certain condition you can use a CKQuery.

let predicate = NSPredicate(format: "%K == %@", "name", "T-Rex")
let query = CKQuery(recordType: "Dinosaur", predicate: predicate)
publicDB.performQuery(query, inZoneWithID: nil) {
(records, error) -> Void in
guard let records = records else {
print("Error querying records: ", error)
return
}
print("Found \(records.count) records matching query")
}

There are quite a few restrictions on what can be done in an NSPredicate for a CKQuery, which are mostly spelled out in CKQuery’s Class Reference.*

CKRecords are the building-blocks of CloudKit and are pretty straight forward and easy to use. With a few quick lines of code, you can easily access a CKDatabase to get access to its Convenience API methods to save, retrieve and delete records. With just this knowledge, you have enough to build an app with a remote database all using native iOS code!

Notes:

  • The one thing I’ve run into not spelled out in CKQuery’s Class Reference is that you cannot use nil in your predicates.

--

--