How do you get the first transaction for NSPersistentHistoryChangeRequest

I am trying to add persistent history tracking to a macOS and iOS app. Both apps use Core Data in documents (UIManagedDocument for iOS and BSManagedDocument for macOS since NSPersistentDocument isn't compatible with UIManagedDocument). My hope is to one day allow the apps to share documents through iCloud (though I'm not hopeful that will happen anytime soon as I have been trying to get Core Data documents to play nicely in iCloud since iCloud first came out…).


Currently the only documentation that exists for any of the persistent history stuff it what is in the WWDC'17 video (session 210) and a couple code comments that are in the headers. Looking at the docs in Xcode's doc browser or on the dev portal only gives you the names of classes and methods.


In the video they use the classic shoebox style Core Data and do the updates in a view controller. Since my apps use documents and they have various screens with different data source instances I moved the update code to the document level. The document's context gets sent to all of the data sources so if the context is updated by the document the fetched results controllers should all see the changes and update as needed.


Currently when my update code runs I get a super useful error message on macOS and iOS: "The operation couldn’t be completed. (Foundation._GenericObjCError error 0.)"


`fetchHistory(after: )` takes an optional persistent history token so it would seem to make sense that passing nil for the token would return the most recent transaction. Since the docs don't have any and I can only see the little bit of code around the 32:20 mark of the video I don't know for sure if the token is getting filled in when the fetched results controller is told to `performFetch()` and that is why their code works and mine doesn't or if I am missing something else that is causing an issue


My code for the update bit is:

   @objc func updateContext() {
        if currentToken == nil {
            // Must have just opened the document, need to get the most recent token somehow…
        }
        let fetchRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: currentToken)
        guard let context = managedObjectContext else { return }
        context.performAndWait {
            do {
                let historyResult = try context.execute(fetchRequest) as! NSPersistentHistoryResult
                let history = (historyResult.result as! [NSPersistentHistoryTransaction])
                for transaction in history {
                    context.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
                    self.currentToken = transaction.token
                }
            } catch {
                print("Something went wrong while updating the context: \(error.localizedDescription)")
            }
        }
    }

When the persistent store is added I add true for the NSPersistentHistoryTrackingKey with :


var newOptions = storeOptions ?? [AnyHashable : Any]()
newOptions[NSPersistentHistoryTrackingKey] = true as NSNumber


I have looked at the docs for NSFecthedResultsController, NSManagedObjectContext, NSPersistentStoreCoordinator, and NSPersistentStore and haven't found anything that looks like `mostRecentTransaction` anywhere.


Does anyone have any info at all on how to use Persistent History in Core Data?

Replies

I have also tried fetching history after `.distantPast` and get the same error…


I have also verified that the persistent store has the NSPersistentHistoryTrackingKey key turned on in its options:


po managedObjectContext.persistentStoreCoordinator?.persistentStores.first?.options

▿ Optional<Dictionary<AnyHashable, Any>>

▿ some : 2 elements

▿ 0 : 2 elements

▿ key : AnyHashable("NSReadOnlyPersistentStoreOption")

- value : "NSReadOnlyPersistentStoreOption"

- value : 0

▿ 1 : 2 elements

▿ key : AnyHashable("NSPersistentHistoryTrackingKey")

- value : "NSPersistentHistoryTrackingKey"

- value : 1

I had the same problem. Looks like the reason why my `NSManagedObject` could not get any persistent history transactions (failed with `nilError`) was because it was initialized directly instead of using `NSPersistentContainer` API. Good news is - even if you can't replace all your `NSManagedObjectContext`s initializations (it would be a hassle in my case since we're using MagicalRecord), you can still access the history and merge changes from it into your contexts. Just create new `NSManagedObject` for fetching the history via `NSPersistentContainer` API.

You could use for example `NSPersistentContainer.performBackgroundTask((NSManagedObjectContext) -> Void)` and voilà you will get your transactions 🎉.