I'm building an application that makes use of NSPersistentCloudKitContainer
. The app doesn't have sharing functionality and its only backend functionality is to use the cloudkit container to sync data across a user's devices. The setup is fairly barebones, instantiating a container, setting up a single store description, and loading the stores.
My big question: Do I need to do anything with persistent history tracking? I have yet to find a concrete answer to this question but from what I can tell, persistent history tracking is used for merging in changes that happen in one target, such as an extension, into another. It doesn't sound like I need it to take full advantage of iCloud sync.
You DO need to enable persistent history tracking so that the device is able to catch up if a user enables/disables/re-enables your Apps use of iCloud by turning off your Apps access to iCloud Drive or via some feature you've implemented.
NSPersistentCloudKitContainer
uses the history and handles it for you behind the scenes. You do NOT need to do anything with history, although you can if you want. The Apple Docs are a little fuzzy on that point.
What I did (and deployed) was to give users a switch in the App to allow them to enable/disable the Apps use of iCloud. It is specific to the App on that device and the setting is NOT persisted via ubiquitous defaults to any other device. I prefer not to encourage them to disable their main source of backup. All it does to disable is set the Container Identifier to nil and when enabled to the container ID string as shown below. This was stated as OK to do by an Apple CloudKit engineer in the Apple Developer Forums. When a user toggles iCloud ON/OFF (after displaying warnings cautioning the user not to disable the only form of backup by switching iCloud storage OFF), I show a message that the App will shut down and they should restart after it does. Then the app exits().
*** WARNING!! (April 2025) I no longer think it is OK to always use NSPersistentCloudKitContainer
with the Container Identifier set to NIL. We started getting complaints from users that their data was gone and not recoverable. These were users who DID NOT enable iCloud. It seems that starting with iOS 18 Apple started deleting data from the local NSPersistentCloudKitContainer
if the user is NOT logged into iCloud on the assumption that it is "in the cloud". Which it is NOT if they are not actually using iCloud. Apparently they changed the behavior for some kind of security / data leak reasons. Therefore it is better to use NSPersistentContainer when they are NOT using iCloud.
DO ALWAYS use persistent history and if you are giving users an option to switch using iCloud on/off then switch containers (between persistent and persistentCloudKit) and make them re-start. And NEVER delete the History, let the NSPersistentContainer underlying code handle it. Apple seems to manage it somehow.
The now recommended way for any version of iOS but definitely iOS 18 and later:
@objc func initCoreDataStack()
{
if(useiCloud) {
pc = NSPersistentCloudKitContainer(name:"YourContainerName")
}
else {
pc = NSPersistentContainer(name:"YourContainerName")
}
guard let description = pc?.persistentStoreDescriptions.first else {
fatalError("*** CoreDataUtil - could not find persistent store description.")
}
description.setOption(true as NSNumber, forKey:NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey:NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.setOption(true as NSNumber, forKey:NSMigratePersistentStoresAutomaticallyOption)
description.setOption(true as NSNumber, forKey:NSInferMappingModelAutomaticallyOption)
//
// If user wants iCloud Storage set container id.
//
if(useiCloud == true) {
coreDataLogger.log("*** User is using iCloud")
let cloudKitContainerIdentifier = "iCloud.my.container"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier:cloudKitContainerIdentifier)
description.cloudKitContainerOptions = options
}
else {
coreDataLogger.log("*** User is NOT using iCloud")
}
pc?.persistentStoreDescriptions.first?.url = storeURL()
pc?.viewContext.automaticallyMergesChangesFromParent = true
pc?.loadPersistentStores { _, error in
if let error = error as NSError? {
self.coreDataLogger.error("*** loadPersistentStore ERROR: \(error, privacy:.public), \(error.userInfo, privacy:.public)")
}
// Do any additional data prep stuff once MOC is ready
....
// Notify the rest of the App that the data is ready
self.sendMOCReadyNotificationForPlatform()
}
(Old way - OK Before iOS 18) Set your CoreData stack like this:
@objc func initCoreDataStack()
{
guard let description = pc.persistentStoreDescriptions.first else {
fatalError("*** CoreDataUtil - could not find persistent store description.")
}
description.setOption(true as NSNumber, forKey:NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey:NSPersistentStoreRemoteChangeNotificationPostOptionKey)
//
// If user wants iCloud Storage set id, else set it to nil
// but with history on (see above) so that if they enable it
// later there is a history of changes.
//
if(useiCloud == true) {
let cloudKitContainerIdentifier = "iCloud.your.container"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier:cloudKitContainerIdentifier)
description.cloudKitContainerOptions = options
}
else {
description.cloudKitContainerOptions = nil
}
pc.persistentStoreDescriptions.first?.url = storeURL()
pc.viewContext.automaticallyMergesChangesFromParent = true
pc.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
// Set this here so that updates by consumers are dynamic
self.pc.viewContext.automaticallyMergesChangesFromParent = true
// My code does this for consumers of this singleton.
self.sendMOCReadyNotificationForPlatform()
}
}