4

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.

barndog
  • 6,975
  • 8
  • 53
  • 105
  • I believe persistent history is used to sync with all external changes, like an app extension, or a remote database (CloudKit) – EmilioPelaez Mar 16 '22 at 08:39
  • 1
    You'll find that more recently (and I'm sorry I cannot quote on when this came into effect) but `NSPersistentHistoryTracking` is automatically implemented for each `NSPersistentCloudKitContainer`. What you might need to be aware of is that if your store (for example the sqlite file) existed before you implemented `NSPersistentCloudKitContainer` (i.e. its was simply `NSPersistentContainer`) then history tracking would not have been automatically implemented and therefore only newly inserted objects will register with CloudKit. – andrewbuilder Mar 16 '22 at 16:32
  • That actually sounds right, I just can't find anything that confirms that's the case – barndog Mar 16 '22 at 17:26
  • 1
    I couldn’t find a source either, but when creating a fresh project with both Core Data + CloudKit enabled and printing out the store description options without making any changes, I could see `NSPersistentHistoryTrackingKey: 1` so it appears to be enabled by default. – Martin Nov 20 '22 at 20:36

1 Answers1

8

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 flips this I instruct them to fully restart the App. So far it seems harmless and in all my testing it works fine.

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()
    }
}
Cliff Ribaudo
  • 8,932
  • 2
  • 55
  • 78