Designing for CloudKit

CloudKit is a way to move structured data between your app and iCloud. Whereas document-based storage operates on files, CloudKit operates on records. A record is a dictionary of key-value pairs, with each key representing a field of the record. The value of each field is usually a simple data type such as a string, number, or date, but you can also store entire files or arbitrary blocks of data. Fields can also store references to other records, making it possible to define relationships between records.

With CloudKit, you decide when to move data from your app to iCloud and from iCloud to your app. Although CloudKit provides facilities to keep you informed when changes occur, you must still fetch those changes explicitly. Because you decide when to fetch and save data, you are responsible for ensuring that data is fetched at the right times and in the right order, and you are responsible for handling any errors that arise.

For detailed information about the classes of the CloudKit framework, see CloudKit Framework Reference. For how to enable CloudKit in your Xcode project and access CloudKit Dashboard, read CloudKit Quick Start.

Enabling CloudKit

Before you can use CloudKit, your app must enable the iCloud capability for your app, as described in Request Access to iCloud Using Xcode Capabilities. Configuring the CloudKit service adds the appropriate entitlements to your app and configures your app with a default container based on its bundle ID.

Containers and Databases

Like other iCloud technologies, CloudKit organizes data using containers. A container serves as the local representation of your app’s iCloud storage. When starting your project, you request entitlements for each of their containers. At runtime, you can perform tasks against a specific container using a CKContainer object.

Each container is divided into public and private databases, each of which is represented by a CKDatabase object. Any data written to the private database is visible only to the current user and is stored in that user’s iCloud account. Data written to the public database is visible to all users of the app and is stored in the app’s iCloud storage.

For a running CloudKit app, a container’s public database is always readable, even when there is no active iCloud account configured on the device. Saving records to the public database and accessing the private database in any way requires the presence of an active iCloud account on the device. If your app does more than read data from the public database, check for an active iCloud account at startup and adjust your interface accordingly. For example, if no iCloud account is available, disable any UI for saving records and notify the user that saving records requires an active iCloud account.

For information about how to check for the availability of an iCloud account and respond to changes to the current account, see Prepare Your App to Use iCloud.

Managing Data in CloudKit

Inside the public and private databases, you organize your app’s data using records and use them to move data between your app and iCloud. Records are dictionaries of key-value pairs represented by the CKRecord class. Each record has a record type, which is a string you use to differentiate between records with different types of information. Each key-value pair represents a field of the record and that field’s corresponding value. Fields can contain simple types such as strings, numbers, and dates or more complex types such as locations, references to other records, or entire files.

Apps must fetch the records they need to run and save changes to those records explicitly. Nothing is automatic. You can initiate fetch and save operations using either operation objects or the convenience methods of the CKContainer and CKDatabase classes. Operation objects can operate on multiple records at once and can be configured with dependencies to ensure that records are saved in the proper order. Operation objects are based on the NSOperation class and can be integrated with your app’s other workflows easily. Fetch operations require that you already know the ID of the record you want.

If you do not know the ID of a record, CloudKit provides the ability to search for records using a predicate. A predicate-based search lets you locate records whose fields contain certain values. You use this predicate with a CKQuery object to specify both the search criteria and sorting options for the returned records. You then execute the query using a CKQueryOperation object.

Another search option is to let the server notify you when certain parts of the database change. Subscriptions act like a persistent query on the server. You configure a CKSubscription object much like you do a CKQuery object, but instead of executing that query explicitly, you save the subscription to the server only once. After that, the server sends push notifications to your app whenever a change occurs that matches your search criteria. For example, you can use subscriptions to detect the creation or deletion of records or to detect when the field of a record changed to a specific value. Upon receiving the push notification from the server, you can fetch the changed record and update your app.

For detailed information about how to use records, operation objects, and the other classes of the CloudKit framework, see CloudKit Framework Reference.

The Development and Production Environments

CloudKit provides separate development and production environments for your record types and data. The development environment is a more flexible environment that is available only to members of your development team. When your app adds a new field to a record and saves that record in the development environment, the server updates the schema information automatically. You can use this feature to make changes to your schema during development, which saves time. One caveat is that after you add a field to a record, the data type associated with that field cannot be changed programmatically. To change a field’s type, you must delete the field in CloudKit Dashboard and add it again with the new type.

Prior to deploying your app, you migrate your schema and data to the production environment using CloudKit Dashboard. When running against the production environment, the server prevents your app from changing the schema programmatically. You can still make changes with CloudKit Dashboard but attempts to add fields to a record in the production environment result in errors.

During development, Xcode automatically points your app to the development environment. Before you ship your app, configure your app using the distribution workflow. In this workflow, Xcode lets you choose whether you want to target the development or production environment and adds the com.apple.developer.icloud-container-environment entitlement to your app with the value you selected. Prior to shipping, be sure to configure your app for the production environment. Apps that target the development environment will be rejected by the App Store.

The Basic CloudKit Workflow

In your app, you use CloudKit to retrieve data from iCloud, make changes, and write those changes back. The process for retrieving data from a CloudKit database is as follows:

  1. Get the CKContainer object corresponding to the iCloud container whose data you want to access.

  2. Get the CKDatabase object that corresponds to the database (public or private) that contains the records.

  3. Identify the records you want to fetch:

    • If you already know the IDs of the records you want, create a CKFetchRecordsOperation object to retrieve them.

    • If you do not know the record IDs, create a CKQuery object with the search criteria that matches the records you want. Then create a CKQueryOperation object to execute that query.

  4. Assign blocks to the appropriate completion handlers of your operation object. Use your blocks to process the results.

  5. Pass your operation object to the addOperation: method of your CKDatabase object. Calling this method enqueues the operation for execution. The operation object then delivers the results asynchronously to your completion blocks.

To save a record that you have created or modified, create a CKModifyRecordsOperation object with the record that you want to save. Execute that operation object in the same way you did when fetching records, by calling the addOperation: method of the appropriate CKDatabase object. As with fetch operations, a save operation executes asynchronously and reports status to the completion handler blocks you specify. Use those blocks to process the results and manage any errors.

It is your responsibility to decide when to fetch the records you need and when to save your changes; your role also includes ensuring the validity of the data you save. CloudKit saves each record atomically. If you need to save a group of records in a single atomic transaction, save them to a custom zone, which you can create using the CKRecordZone class. Zones are a useful way to arrange a discrete group of records but are supported only in private databases. Zones cannot created in a public database.

Tips for Defining Your App’s Record Types

When defining your app’s record types, it helps to understand how you use those record types in your app. Here are some tips to help you make better choices during the design process:

Future Proofing Your Records

As you design the record types for your app, make sure those records meet your needs but do not restrict you from making changes in the future. It is easy to add new fields to a record, however, a field cannot be removed or its data types changed after you deploy your records to a production environment. There are some ways that you can make updating your app easier in the future: