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
}
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.