A Primer on CloudKit Containers and Databases

CloudKit is an amazing framework, giving developers direct access to Apple's iCloud servers. Several of Apple's flagship applications are powered by CloudKit, including Photos and Notes. It shows that Apple's iCloud infrastructure is robust and scalable, demonstrating that CloudKit is a viable solution for storing data in the cloud.

Before you decide to integrate CloudKit in a project, it's important to understand the ins and outs of the framework. You need to spend some time learning about the terminology, best practices, and common pitfalls.

In this tutorial, I'd like to take a moment to talk about iCloud containers and databases, fundamental components of every application powered by CloudKit.

iCloud Containers

Every application that integrates with iCloud has access to an iCloud container. The container encapsulates the data that's associated with your application, stored on Apple's iCloud servers. An iCloud container is in some ways similar to the application container on the user's physical device. Both silo the data of your application from the data of other applications on the user's device or Apple's iCloud servers.

There's one difference, though. An application can have access to multiple iCloud containers. As I mentioned earlier, every application has access to its own container, the default container. Later in this tutorial, I show how an application can access to other iCloud containers.

Enabling CloudKit

The CloudKit framework makes interacting with iCloud containers straightforward. Let me show you how this works. Fire up Xcode and create a new project by choosing the Single View App template.

Setting Up the Project

Provide a sensible name and organization identifier. The bundle identifier of your application determines the identifier of the application's iCloud container.

Setting Up the Project

We first need to enable the iCloud capability for the application in the target's Capabilities tab.

Enabling iCloud

Xcode creates an entitlements file for the application and adds the iCloud entitlement to it. It also adds the iCloud feature to the corresponding App ID.

Enabling iCloud

To enable CloudKit for the target, we check the CloudKit checkbox under Services.

Enabling CloudKit

Xcode creates a default container on your behalf and links the target against the CloudKit framework.

Accessing the Default Container

The majority of applications only need access to the application's default iCloud container. The CloudKit framework makes that easy. Open ViewController.swift and add an import statement for the CloudKit framework.

import UIKit
import CloudKit

class ViewController: UIViewController {

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

The application can access the default container through the default() method of the CKContainer class, returning a CKContainer instance. A CKContainer instance represents an iCloud container.

import UIKit
import CloudKit

class ViewController: UIViewController {

    // MARK: - View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        // Default Container
        let container = CKContainer.default()

        if let containerIdentifier = container.containerIdentifier {
            print(containerIdentifier)
        }
    }

}

To verify that the default() method returns a reference to the application's default container, we inspect the container's identifier. It should match the identifier in the Capabilities > iCloud section.

iCloud.com.cocoacasts.Scribbles

Accessing Other iCloud Containers

As I mentioned earlier, it's possible for your application to access the iCloud container of another application as long as that application is tied to the same developer account. That's the only limitation. Let me show you how that works.

Revisit the Capabilities tab of the target and open the iCloud section. In the Containers section, select Specify custom containers and check the iCloud containers your application needs access to.

Accessing Other iCloud Containers

Accessing the iCloud container in code isn't too difficult either. Instead of asking the CKContainer class for the application's default container, we initialize a CKContainer instance by invoking the init(identifier:) initializer.

// Custom Container
let container = CKContainer(identifier: "iCloud.com.cocoacasts.notes")

The identifier should correspond with the iCloud container identifier you selected in the Capabilities > iCloud section. Xcode automatically adds the iCloud container identifier to the target's entitlements file. And remember that the iCloud container identifier is case sensitive.

If you initialize a CKContainer instance with an invalid container identifier or with the container identifier of an iCloud container your application doesn't have access to, then you still receive a valid CKContainer object. Once you start interacting with the CKContainer object, though, a runtime error is thrown.

This is the error you see in Xcode's console when passing an invalid container identifier to init(identifier:).

<CKError 0x1c044ab00: "Bad Container" (1014); "Couldn't get container configuration from the server for container "iCloud.com.cocoacasts.Notes"">

Creating a Custom iCloud Container

If you're creating a suite of applications that need access to the same iCloud container, then you have two options. You can give the applications access to the iCloud container of one of the suite's applications. We discussed this option in the previous section.

There's another option, though. You can also create a custom iCloud container that isn't associated with an application. I like this approach over giving an application access to the default iCloud container of another application.

Revisit the Capabilities > iCloud section and click the small plus button at the bottom of the table listing the iCloud containers.

Creating a Custom iCloud Container

Choose a fitting iCloud container identifier and click OK. Xcode takes care of the details behind the scenes, including adding the iCloud container identifier to the target's entitlements file.

CloudKit Databases

An iCloud container contains several databases. The CloudKit framework defines three database types:

  • private database
  • public database
  • shared database

Private Database

An iCloud container contains a private database for every user of your application signed in to their iCloud account. Because a private database is associated with a user, it can only be accessed if the user is signed in to their iCloud account.

Apple is a company that takes privacy seriously and that becomes obvious when you start working with CloudKit. Developers don't have access to the data stored in the user's private database. This makes debugging harder, but it also means that you don't need to worry about securing the user's private data.

Your application can ask a CKContainer instance for a reference to the user's private database through the privateCloudDatabase property.

// Private Database
container.privateCloudDatabase

The CKContainer class also defines the database(with:) method through which you can access the user's private database.

// Private Database
container.database(with: .private)

A container always returns a reference to a CKDatabase instance if it's asked for the user's private database, even if the user isn't signed in to their iCloud account. However, it's obvious that any interactions with such a private database result in a runtime error. This means that you should always ask the container for the user's account status. You can read more about this topic elsewhere on Cocoacasts.

Public Database

The data stored in a public database can be read by every user, even if that user isn't signed in to their iCloud account. An iCloud container contains one public database, which you can access through its publicCloudDatabase property.

// Public Database
container.publicCloudDatabase

You can also obtain a reference to the public database by passing .public to the database(with:) method of the CKContainer class.

// Public Database
container.database(with: .public)

Because a CloudKit record always has an owner, the user that created the record, users can only write data to the public database if they're signed in to their iCloud account.

Shared Database

The shared database of an iCloud container is only accessible if the user is signed in to their iCloud account. The shared database is used to share one or more records from a user's private database with other users of your application. Your application accesses the shared database of a container through the sharedCloudDatabase property.

// Shared Database
container.sharedCloudDatabase

You can also obtain a reference to the shared database by passing .shared to the database(with:) method of the CKContainer class.

// Shared Database
container.database(with: .shared)

A container's shared database is in some ways similar to the user's private database. A container always returns a reference to a CKDatabase instance when it's asked for its shared database, even if the user isn't signed in to their iCloud account. Interactions with the shared database result in a runtime error if the user isn't signed in to their iCloud account.

Sharing records through the shared database is a more advanced topic and beyond the scope of this tutorial. What you need to remember, though, is that the user doesn't own the data stored in the shared database. The shared database stores shares and each share references a record of the user's private database. A user can only access such as share if they have the required permissions.

Interacting With Databases

Even though an iCloud container contains a public database, a shared database, and many private databases, from the application's perspective it contains one public, one shared, and one private database. The private database is the private database of the currently signed in user. The CloudKit framework takes care of the finer details, ensuring that the application can only access the data the user is allowed to access.

What's Next

You should now have a good understanding of iCloud containers and databases. CloudKit is a flexible framework that's fairly easy to pick up.

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By