Is History Tracking in Cloudkit shared database needed?

I’ve setup the Cloudkit persistent container with private and shared database (see code below). I’ve enabled NSPersistentHistoryTrackingKey to true also for .shared database. I’ve noticed in the example from Apple that the History Tracking is only enabled in .private but not for .shared.

Questions:

For a CloudKit setup to sync (a) between owners’ own devices (only private database), and (b) between multiple iCloud Users through .private and .shared databases, Do I need to enable history tracking for .shared database if I want to check the remote changes in the .shared database (or is the history tracking of the .private database of the owner also accessible in the .shared database)?

========================

let APP_BUNDLE_IDENTIFIER = Bundle.main.bundleIdentifier!
let APP_GROUP_IDENTIFIER = "group." + APP_BUNDLE_IDENTIFIER

private func setupPersistentContainer(_ container: NSPersistentCloudKitContainer? = nil, isStartup: Bool = true) -> NSPersistentCloudKitContainer {

  let container = container ?? getCloudKitContainer(name: CORE_DATA_DATA_MODEL_NAME)

  let defaultDirectoryURL: URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_IDENTIFIER) ?? NSPersistentCloudKitContainer.defaultDirectoryURL()

  let privateDataStoreURL = defaultDirectoryURL.appendingPathComponent("PrivateDataStore.store")
  let sharedDataStoreURL = defaultDirectoryURL.appendingPathComponent("SharedDS.store")

  // MARK: Private Store configuration
  let privateDataStoreDescription = NSPersistentStoreDescription(url: privateDataStoreURL)
  privateDataStoreDescription.configuration = "PrivateDataStore"
  // Enable lightweight migration
  privateDataStoreDescription.shouldInferMappingModelAutomatically = true
  privateDataStoreDescription.shouldMigrateStoreAutomatically = true
  // Turn History Tracking
  privateDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
  let logOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: CLOUDKIT_LOG_CONTAINER_ID)
  logOptions.databaseScope = .private
  privateDataStoreDescription.cloudKitContainerOptions = logOptions
  // turn on remote change notifications
  privateDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
  container.persistentStoreDescriptions = [privateDataStoreDescription]

  // MARK: Share Store configuration
  let sharedDataStoreDescription = NSPersistentStoreDescription(url: sharedDataStoreURL)
  sharedDataStoreDescription.configuration = "SharedDS"
  // MARK: Enable lightweight migration
  sharedDataStoreDescription.shouldInferMappingModelAutomatically = true
  sharedDataStoreDescription.shouldMigrateStoreAutomatically = true
  sharedDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
  let sharedOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: CLOUDKIT_LOG_CONTAINER_ID)
  sharedOptions.databaseScope = .shared
  sharedDataStoreDescription.cloudKitContainerOptions = sharedOptions
  // turn on remote change notifications
  sharedDataStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
  container.persistentStoreDescriptions.append(sharedDataStoreDescription)

  self.stores = [StoreType : NSPersistentStore]()
  container.loadPersistentStores(completionHandler: { [self] (storeDescription, error) in
      if let error = error as NSError? {
          print(error)
      }

      if let cloudKitContainerOptions = storeDescription.cloudKitContainerOptions {
          if cloudKitContainerOptions.databaseScope == .private {
              self.stores[.privateStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? privateDataStoreURL)
          } else if cloudKitContainerOptions.databaseScope == .shared {
              self.stores[.sharedStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? sharedDataStoreURL)
          }
      } else {
          self.stores[.privateStore] = container.persistentStoreCoordinator.persistentStore(for: storeDescription.url ?? privateDataStoreURL)
      }
  })

  /// Automatically merge changes in background context into View Context
  /// Since we always use background context to save and viewContext to read only.  The store values should always trump
  container.viewContext.automaticallyMergesChangesFromParent = true
  container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

  // Create separate context for read and write
  container.viewContext.name = VIEW_CONTEXT_NAME
  container.viewContext.transactionAuthor = self.contextAuthor

  self.setQueryGeneration(context: container.viewContext, from: .current)

  return container
}
Answered by DTS Engineer in 796065022

Yes, you need to enable the history tracking for all the CloudKit-backed stores. This is demonstrated in our following sample:

Concretely, it uses sharedStoreDescription for the store associated with the CloudKit shared database, which is a copy of privateStoreDescription, which has NSPersistentHistoryTrackingKey being set to true, as shown in the following code:

        guard let privateStoreDescription = container.persistentStoreDescriptions.first else {
            fatalError("#\(#function): Failed to retrieve a persistent store description.")
        }
        privateStoreDescription.url = privateStoreFolderURL.appendingPathComponent("private.sqlite")
        
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        let cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: gCloudKitContainerIdentifier)

        cloudKitContainerOptions.databaseScope = .private
        privateStoreDescription.cloudKitContainerOptions = cloudKitContainerOptions
                
        /**
         Similarly, add a second store and associate it with the CloudKit shared database.
         */
        guard let sharedStoreDescription = privateStoreDescription.copy() as? NSPersistentStoreDescription else {
            fatalError("#\(#function): Copying the private store description returned an unexpected value.")
        }

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Accepted Answer

Yes, you need to enable the history tracking for all the CloudKit-backed stores. This is demonstrated in our following sample:

Concretely, it uses sharedStoreDescription for the store associated with the CloudKit shared database, which is a copy of privateStoreDescription, which has NSPersistentHistoryTrackingKey being set to true, as shown in the following code:

        guard let privateStoreDescription = container.persistentStoreDescriptions.first else {
            fatalError("#\(#function): Failed to retrieve a persistent store description.")
        }
        privateStoreDescription.url = privateStoreFolderURL.appendingPathComponent("private.sqlite")
        
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

        let cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: gCloudKitContainerIdentifier)

        cloudKitContainerOptions.databaseScope = .private
        privateStoreDescription.cloudKitContainerOptions = cloudKitContainerOptions
                
        /**
         Similarly, add a second store and associate it with the CloudKit shared database.
         */
        guard let sharedStoreDescription = privateStoreDescription.copy() as? NSPersistentStoreDescription else {
            fatalError("#\(#function): Copying the private store description returned an unexpected value.")
        }

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Is History Tracking in Cloudkit shared database needed?
 
 
Q