Communicating with CloudKit for the first time

When your app launches for the very first time, it should immediately make itself known to CloudKit so it can subscribe to changes. Technically you don't have to do this because you can implement your own logic to retrieve data from CloudKit as you need it. However, an efficient CloudKit implementation will work flawlessly when there is no internet connection, which means that it's probably a good idea to store all data locally by writing it to a database such as Core Data.

The recommended way to subscribe to database changes is to keep track of whether your app is already subscribed locally. You can keep track of this by storing a variable in the local UserDefaults store. UserDefaults is perfect for storing variables such as this. Open CloudStore.swift in the MustC project and add the following computed property to it:

var isSubscribedToDatabase: Bool {
  get { return UserDefaults.standard.bool(forKey: "CloudStore.isSubscribedToDatabase") }
  set { UserDefaults.standard.set(newValue, forKey: "CloudStore.isSubscribedToDatabase") }
}

This property proxies the standard UserDefaults through its custom getters and setters. A construction such as this is very convenient since it makes using this property simple and straightforward even though there's a bit more logic going on behind the scenes.

With the isSubscribedToDatabase property in place, you should also implement a method that's responsible for subscribing the app to changes in the CloudKit database. Add the following implementation to CloudStore.swift:

extension CloudStore {
  func subscribeToChangesIfNeeded(_ completion: @escaping (Error?) -> Void) {
    // 1
    guard isSubscribedToDatabase == false else {
      completion(nil)
      return
    }

    // 2
    let notificationInfo = CKSubscription.NotificationInfo()
    notificationInfo.shouldSendContentAvailable = true

    // 3
    let subscription = CKDatabaseSubscription(subscriptionID: "private-changes")
    subscription.notificationInfo = notificationInfo

    // 4
    let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: [])

    // 5
    operation.modifySubscriptionsCompletionBlock = { [unowned self] subscriptions, subscriptionIDs, error in
      if error == nil {
        self.isSubscribedToDatabase = true
      }

      completion(error)
    }

    // 6
    privateDatabase.add(operation)
  }
}

Let's go over the preceding code step by step, so you understand what happens in this snippet:

  1. If the application has already subscribed to changes, there is no need to subscribe again. This means that you can call the completion closure without any errors.
  2. When there are changes in the database, CloudKit will send a silent push notification to the app. Silent push notifications can be used to let the app know that new data is available. This mechanism is very similar to background fetch, which is covered in Chapter 10, Being Proactive with Background Fetch.
  3. Every subscription in CloudKit is created as an instance of CKDatabaseSubscription. The notification settings that were configured in step two are attached to the database subscription.
  4. This step creates an instance of CKModifySubscriptionsOperation. CloudKit uses a concept called Operations to manage the operations that you want the database to execute. Subscribing to changes is an example of such an operation, but fetching changes from the database or sending data to the database are also examples of operations. You can read more about Operations in Chapter 25, Offloading Tasks with Operations and GCD.
  5. This step defines a closure that is called when the operation to subscribe to changes has finished. If this operation completes without error, the app is subscribed to changes and the status is persisted to UserDefaults. Every CloudKit operation has one or more of these closure callbacks that you can implement to respond to certain events that can occur during an operation.
  1. The last step is to add the operation to the private database. Since all data will be stored in the private moviesZone that you created earlier, the appropriate database to use for the subscription is the private database.

Your app is now subscribed to changes from the CloudKit database. The next step is to make sure your app receives and handles updates from the CloudKit server.