Retrieving changes from CloudKit

To fetch changes that occurred after a certain point in time, you need to a change token along with your fetch operations. Add the following computed property to the CloudStore class so you can keep track of the change token that represents the last time the app fetched changes from CloudKit:

var privateDatabaseChangeToken: CKServerChangeToken? {
  get { return UserDefaults.standard.serverChangeToken(forKey: "CloudStore.privateDatabaseChangeToken") }
  set { UserDefaults.standard.set(newValue, forKey: "CloudStore.privateDatabaseChangeToken") }
}

This computed property uses a helper method on UserDefaults to store CKServerChangeToken.

If you're curious to see how the computed property is implemented, make sure to look at the UserDefaults.swift file in the project from the code bundle.

Add the following code to CloudStore.swift to implement the logic to fetch database changes:

extension CloudStore {
  func fetchDatabaseChanges(_ completionHandler: @escaping (Error?) -> Void) {
    // 1
    let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: privateDatabaseChangeToken)

    // 2
    var zoneIds = [CKRecordZone.ID]()
    operation.recordZoneWithIDChangedBlock = { zoneId in
      zoneIds.append(zoneId)
    }

    // 3
    operation.changeTokenUpdatedBlock = { [weak self] changeToken in
      self?.privateDatabaseChangeToken = changeToken
    }

    // 4
    operation.fetchDatabaseChangesCompletionBlock = { [weak self] changeToken, success, error in
      self?.privateDatabaseChangeToken = changeToken

      if zoneIds.count > 0 && error == nil {
        self?.fetchZoneChangesInZones(zoneIds, completionHandler)
      } else {
        completionHandler(error)
      }
    }

    privateDatabase.add(operation)
  }
}

The previous method retrieves only the record changes from the CloudKit database in the following steps:

  1. CKFetchDatabaseChangesOperation is created. This operation will retrieve changes in the database zones. The latest change token is passed to this operation to make sure it only returns data that has changed since the last successful fetch.
  2. The fetch operation will not return all changes zones at once. Instead, every changed zone is provided one by one, so they should be stored in an array for easy access at a later time.
  1. At certain times in the refresh cycle, the operation will receive a change token from CloudKit. A single fetch operation might result in more than one change token. This could occur if there are many changes to be fetched from the server. In that case, the server will return the changed data in batches, allowing you to process the changes as they come in. It's essential that you store the latest token you have received from the server.
  2. Provide a closure to the operation that should be executed when the fetch operation completes. This method also receives a change token, so you should store this token locally since that token represents the completed fetch operation. If no errors occurred and at least one database zone has changed, a new method is called. This method is responsible for fetching all changes to the database records, and you will implement it next.

Once the server has informed the app about all of the changed zones in the database, the app should then ask CloudKit for the relevant record changes. A single operation can retrieve data across multiple zones so your app won't have to issue many requests to CloudKit if there are several zones that have changes. Add the following extension to CloudStore.swift:

extension CloudStore {
  func fetchZoneChangesInZones(_ zones: [CKRecordZone.ID], _ completionHandler: @escaping (Error?) -> Void) {
    // 1
    var fetchConfigurations = [CKRecordZone.ID: CKFetchRecordZoneChangesOperation.ZoneConfiguration]()

    for zone in zones {
      if let changeToken = UserDefaults.standard.zoneChangeToken(forZone: zone) {
        let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration(previousServerChangeToken: changeToken, resultsLimit: nil, desiredKeys: nil)
        fetchConfigurations[zone] = configuration
      }
    }

    let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zones, configurationsByRecordZoneID: fetchConfigurations)

    // 2
    var changedMovies = [CKRecord]()
    var changedFamilyMembers = [CKRecord]()
    operation.recordChangedBlock = { record in
      if record.recordType == "Movie" {
        changedMovies.append(record)
      } else if record.recordType == "FamilyMember" {
        changedFamilyMembers.append(record)
      }
    }

    operation.fetchRecordZoneChangesCompletionBlock = { [weak self] error in
      for record in changedMovies {
        print(record["title"])
      }

      for record in changedFamilyMembers {
        print(record["name"])
      }

      completionHandler(error)
    }

    // 3
    operation.recordZoneFetchCompletionBlock = { recordZone, changeToken, data, moreComing, error in
      UserDefaults.standard.set(changeToken, forZone: recordZone)
    }

    privateDatabase.add(operation)
  }
}

The preceding method is quite long but it follows a flow that is similar to the other two. Let's walk through this code step by step again:

  1. When you fetch changes for record zones, each zone will have its own change token. This step uses another helper that was added to UserDefaults to conveniently store change tokens for each individual zone. The zones and their change tokens should be provided to CKFetchRecordZoneChangesOperation through a configuration dictionary. The operation itself is initialized with the dictionary and a list of zones that have changed.
  2. The operation executes a callback for every changed record that it receives from the server. To make processing at a later time easier, the records for family members and movies are stored in their own lists. Note that both movies and family members are sent to the app as CKRecord instances. CKRecord has several specific CloudKit properties and methods, but otherwise, it behaves a lot like a dictionary. You will learn more about CKRecord instances later when you store your objects in Core Data.
  3. CKFetchRecordZoneChangesOperation will call a closure for every zone that it processes. This callback receives a lot of arguments, such as the database zone that the closure was called for, the corresponding change token, and whether more data is coming in. The most interesting cases are the database zone and the change token. These two properties are used to store the latest change token for the corresponding zone in UserDefaults.

At this point, the CloudStore object contains all of the logic required to subscribe to database changes, retrieve them, and then print the objects that were fetched. To use CloudStore, you will need to update some code in AppDelegate.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.140.206