Exception when using query generations

I finally got around to trying out the new query generations in iOS 10 but when I turned it on, I started getting exceptions when calling mergeChanges(fromContextDidSave:). My app supports iOS 9 as well so I can't use NSPersistentContainer quit yet. Here's where I am setting up the Core Data stack: (also at https://gist.github.com/davbeck/85adfe8ab723562abf7c998e53f8e9f5)


  /

  static func defaultStoreURL() -> URL? {
  do {
  guard var url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { return nil }
  url.appendPathComponent("EngagementData")

  try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
  url.appendPathComponent("Data.sqlite3")

  return url
  } catch {
  return nil
  }
  }

  public let storeURL: URL?

  public let managedObjectModel: NSManagedObjectModel = {
  let momURL = Bundle(for: APIClient.self).url(forResource: "Realm", withExtension: "momd")!

  return NSManagedObjectModel(contentsOf: momURL)!
  }()

  fileprivate(set) open lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
  let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

  let options = [
  NSMigratePersistentStoresAutomaticallyOption: true,
  NSInferMappingModelAutomaticallyOption: true,
  ]

  if let storeURL = self.storeURL {
  do {
  self.logger.info("storeURL: \(self.storeURL)")

  try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: self.storeURL, options: options)
  return persistentStoreCoordinator
  } catch let error {
  self.logger.error("Could not create NSPersistentStoreCoordinator", error: error)
  }
  }

  /
  try! persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
  return persistentStoreCoordinator
  }()

  fileprivate(set) public lazy var backgroundContext: NSManagedObjectContext = {
  let context = ManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  context.persistentStoreCoordinator = self.persistentStoreCoordinator
  context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
  context.dispatchGroup = self.dispatchGroup
  context.shouldDeleteInaccessibleFaults = true
  if #available(iOS 10, *) {
  do {
  try context.setQueryGenerationFrom(.current)
  } catch {
  self.logger.error("could not activate query generation", error: error)
  }
  }

  return context
  }()

  public func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
  self.backgroundContext.perform {
  block(self.backgroundContext)
  }
  }

  fileprivate(set) public lazy var viewContext: NSManagedObjectContext = {
  let context = ManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
  context.persistentStoreCoordinator = self.persistentStoreCoordinator
  context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
  context.dispatchGroup = self.dispatchGroup
  context.shouldDeleteInaccessibleFaults = true
  if #available(iOS 10, *) {
  do {
  try context.setQueryGenerationFrom(.current)
  } catch {
  self.logger.error("could not activate query generation", error: error)
  }
  }

  return context
  }()


  /

  func viewContextDidSave(_ notification: Notification) {
  self.logger.debug("viewContextDidSave: \(notification.userInfo)")

  self.backgroundContext.perform {
  self.backgroundContext.mergeChanges(fromContextDidSave: notification)
  }
  }

  func backgroundContextDidSave(_ notification: Notification) {
  self.logger.debug("backgroundContextDidSave: \(notification.userInfo)")

  self.viewContext.perform {
  self.viewContext.mergeChanges(fromContextDidSave: notification)
  }
  }


Inside of backgroundContextDidSave, self.viewContext.mergeChanges(fromContextDidSave: notification) causes an EXC_BAD_INSTRUCTION and prints out "More code needs to be written". Am I missing something? What other code needs to be written?!?!

Replies

Unfortunately I don't have anything very useful to add, but I've run into similar issues with query generations.


In my case I am using NSPersistentContainer, and I've seen this crash on iOS 10.1 and watchOS 3.1 (release versions). For me the crash happens sometimes (not usually) when I call save: on my managed object context (the persistent container's viewContext). This calls [NSSQLiteConnection adoptQueryGenerationIdentifier:], presumably to advance the query generation token. I'm getting an EXC_BREAKPOINT crash there, and the same "More code needs to be written" message logged. So this is similar to your scenario—we're both doing something that the documentation states will "automatically advance it to the most recent version for the operation and then reset its query generation to

currentQueryGenerationToken
."


The "More code needs to be written" message is a little troubling because it sure doesn't seem like something users of the API were meant to see. I think I'm going to hold off on using this for now. I'd recommend filing a bug report—I'll be doing the same when I have some time.

Hi!


I know this thread has been a while. I recently run into the same crash as well.


The crash happens relatively randomly and I have no clue how to reproduce it reliably.


My setup contains the "view context" and an "import context". They are both are configured to auto merge and pin to the head...


        try! context.setQueryGenerationFrom(NSQueryGenerationToken.current)
        context.automaticallyMergesChangesFromParent = true


Have you guys figured out some ways to solve the problem? Or, just don't use query generation at all?


Thanks

Bill

This works for me...

        if !DataController.isUITesting {
            _container.viewContext.undoManager = nil
            _container.viewContext.shouldDeleteInaccessibleFaults = true

            do {
                try _container.viewContext.setQueryGenerationFrom(.current)
            } catch {
                fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
            }
        }