skip to Main Content

I want to start learning to use NSAsynchronousFetchRequest by referring to https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/

I have the simplest use case of NSAsynchronousFetchRequest

NSAsynchronousFetchRequest

// Call from UI main thread
func X() {
    let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
    
    let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
        guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
    }

    let coreDataStack = CoreDataStack.INSTANCE
    // backgroundContext created via persistentContainer.newBackgroundContext()
    let backgroundContext = coreDataStack.backgroundContext
    
    backgroundContext.perform {
        do {
            try backgroundContext.execute(asynchronousFetchRequest)
        } catch let error {
            backgroundContext.rollback()
            
            error_log(error)
        }
    }
}

However, running the above code will get me the following error

CoreData`+[NSManagedObjectContext
Multithreading_Violation_AllThatIsLeftToUsIsHonor]:

If I modify the code by using NSFetchRequest directly.

NSFetchRequest

// Call from UI main thread
func X() {
    let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")

    let coreDataStack = CoreDataStack.INSTANCE
    // backgroundContext created via persistentContainer.newBackgroundContext()
    let backgroundContext = coreDataStack.backgroundContext
    
    backgroundContext.perform {
        do {
            let nsPlainNotes = try fetchRequest.execute()
        } catch let error {
            backgroundContext.rollback()
            
            error_log(error)
        }
    }
}

Thing works fine. May I know, what’s wrong with my NSAsynchronousFetchRequest version of code?

This is my CoreDataStack.swift for reference purpose.

CoreDataStack.swift

import CoreData

class CoreDataStack {
    static let INSTANCE = CoreDataStack()
    
    private init() {
    }
    
    private(set) lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "wenote")
        
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
                fatalError("Unresolved error (error), (error.userInfo)")
            }
        })
        
        // So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
        // persistent store.
        container.viewContext.automaticallyMergesChangesFromParent = true
        
        // TODO: Not sure these are required...
        //
        //container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        //container.viewContext.undoManager = nil
        //container.viewContext.shouldDeleteInaccessibleFaults = true
        
        return container
    }()
    
    private(set) lazy var backgroundContext: NSManagedObjectContext = {
        let backgroundContext = persistentContainer.newBackgroundContext()

        // Similar behavior as Android's Room OnConflictStrategy.REPLACE
        backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        // TODO: Not sure these are required...
        //backgroundContext.undoManager = nil
        
        return backgroundContext
    }()
}

Additional information

Do note that, in NSAsynchronousFetchRequest example, even if backgroundContext.perform is not used.

// Call from UI main thread
func X() {
    let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")

    let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
        guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
    }

    let coreDataStack = CoreDataStack.INSTANCE
    // backgroundContext created via persistentContainer.newBackgroundContext()
    let backgroundContext = coreDataStack.backgroundContext

    do {
        try backgroundContext.execute(asynchronousFetchRequest)
    } catch let error {
        backgroundContext.rollback()

        error_log(error)
    }
}

Same fatal error still occur.

Please note that, this fatal error will only be triggered, by editing the schema with Arguments Passed On Launch

-com.apple.CoreData.ConcurrencyDebug 1

enter image description here

I even try to execute some simple project from https://github.com/abhishekbedi1432/Core-Data-Asynchronous-Fetching/tree/master which is using NSAsynchronousFetchRequest.

If I do not enable -com.apple.CoreData.ConcurrencyDebug 1, the sample project from github able to perform asynchronous fetch without issue. However, once the -com.apple.CoreData.ConcurrencyDebug 1 is enabled, it will also be getting the same fatal error.

2

Answers


  1. You have to make a new context either a child or a root parent for present Core Data container, like so:

    let backgroundContext = persistentContainer.newBackgroundContext()
    backgroundContext.parent = persistentContainer.viewContext
    

    Exhaustive explanation of using multiple contexts is here.

    Login or Signup to reply.
  2. For me, the async fetch request works as expected when I provide an estimatedResultCount value to it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search