skip to Main Content

It seems there are 2 ways to perform asynchronous read in CoreData, without blocking main thread UI.


newBackgroundContext + NSFetchRequest

Source : https://www.advancedswift.com/core-data-background-fetch-save-create/

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

// Perform operations on the background context
// asynchronously
context.perform {
    do {
        // Create a fetch request
        let fetchRequest: NSFetchRequest<CustomEntity>

        fetchRequest = CustomEntity.fetchRequest()
        fetchRequest.fetchLimit = 1

        let objects = try context.fetch(fetchRequest)

        // Handle fetched objects
    }
    catch let error {
        // Handle error
    }
}

newBackgroundContext + NSAsynchronousFetchRequest

Source: https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/

let privateManagedObjectContext = persistentContainer.newBackgroundContext()

// Creates a fetch request to get all the dogs saved
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")

// Creates `asynchronousFetchRequest` with the fetch request and the completion closure
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in

    // Retrieves an array of dogs from the fetch result `finalResult`
    guard let result = asynchronousFetchResult.finalResult as? [Dog] else { return }

    // Dispatches to use the data in the main queue
    DispatchQueue.main.async {
        // Do something
    }
}

do {
    // Executes `asynchronousFetchRequest`
    try privateManagedObjectContext.execute(asynchronousFetchRequest)
} catch let error {
    print("NSAsynchronousFetchRequest error: (error)")
}

However, note that, the above code will unfortunately cause fatal error, if I were to enable flag -com.apple.CoreData.ConcurrencyDebug 1. So far, I do not have a good solution to such. For more detail, please refer to Why I am getting Multithreading_Violation_AllThatIsLeftToUsIsHonor for this simplest NSAsynchronousFetchRequest use case?


May I know, what is the difference among newBackgroundContext + NSFetchRequest vs newBackgroundContext + NSAsynchronousFetchRequest?

How should I choose one over another? Thank you.

2

Answers


  1. There are two main features of NSAsynchronousFetchRequest:

    1. We don’t need separated context (background, any) for it, meaning you can execute it on main view context everything else (like creating background context if needed, etc) will be done by API.
      Note: you still need to redirect to main thread in completion block, because it can be called on any queue.

    2. We can track progress of fetching data directly via NSAsynchronousFetchResult.progress if there is fetchLimit set for initial request.

    Login or Signup to reply.
  2. 1. About the __Multithreading_Violation_AllThatIsLeftToUsIsHonor__ exception:

    There is a detailed discussion in this thread:
    CoreData asynchronous fetch causes concurrency debugger error

    The consensus is that it is a bug in CoreData.
    There is a bug report: https://openradar.appspot.com/30692722 which is still open after 8 years at the time of writing.

    2. How to properly use NSAsynchronousFetchRequest

    The API was introduced in 2014 and was discussed in this WWDC video 225_sd_whats_new_in_core_data.
    It is not mentioned whether NSAsynchronousFetchRequest should be used on the main (view) context or on a background context.

    I looked over a couple of random implementations using NSAsynchronousFetchRequest on GitHub and I found examples for both main and background context.

    One thing you must do when using a background context however, is wrapping the fetch execution in in a perform block (documentation).
    In the article you linked and in the example that you excerpted above, this is missing!
    This is how it should look like:

    privateManagedObjectContext.perform {
        do {
            try privateManagedObjectContext.execute(asynchronousFetchRequest)
        } catch let error {
            print("error trying to fetch saving objects:", error.localizedDescription)
        }
    }
    

    There is another potential issue with the same article, so take it with a grain of salt:

    DispatchQueue.main.async {
        // here the objects in result (belongs to private context) are
        // accessed on the main queue – the whole point is to *not* do that!
        // one might get away with it because it is only read access to id
        // but good luck debugging this...
        let dogs: [Dog] = result.lazy
            .flatMap { $0.objectID }
            .flatMap { mainManagedObjectContext.object(with: $0) as? Dog }
        // ...
    

    The way I understand NSAsynchronousFetchRequest is that it is best used from the main context and that its purpose is actually to hide the background context business from you.

    So: mainContext + NSAsynchronousFetchRequest

    3. How should I choose one over another?

    It seems to me that NSAsynchronousFetchRequest was created with good intention to simplify asynchronous core data fetching for us. And to that end you may well use it to your advantage, especially if you need to deal with progress and cancellation.

    However I probably would not use it in my project because

    • the documentation is sparse
    • it is not well maintained (open bugs since forever)
    • it is not well adopted (e.g. the popular and excellent CoreData wrapper CoreStore does not use it)

    One last thought – before going into asynchronous fetching at all make sure that you really need it. It may be better to optimise the performance of your query, data model or batch settings first.

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