5

Apple has shown us how to perform heavy write operation using background thread (by using newBackgroundContext) in their official earth quake example - https://github.com/yccheok/earthquakes-WWDC20

But, what about heavy read operation? (millions of rows for stress test purpose)

We would also like our app UI to be responsiveness, when we are launching the app for first time, and the app is reading a large amount of data from CoreData.

The following is the code snippet which is using NSFetchedResultController.


UI is not responsiveness if there are a lot of rows

let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
                                            managedObjectContext: persistentContainer.viewContext,
                                            sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = fetchedResultsControllerDelegate

// Perform the fetch.
do {
    // This statement tooks some time to complete if you have a lot of rows.
    try controller.performFetch()
} catch {
    fatalError("Unresolved error \(error)")
}

UI is not responsiveness if there are a lot of rows

We try to perform controller.performFetch() using background thread. Still, but not sure why, the UI is still not responsiveness. My guess is that, after NSFetchedResultsController is occupied UI main thread, to perform some time consuming I/O read operation.

let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
                                            managedObjectContext: persistentContainer.viewContext,
                                            sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = fetchedResultsControllerDelegate

DispatchQueue.global(qos: .background).async {
    // Perform the fetch.
    do {
        // This statement tooks some time to complete if you have a lot of rows.
        try controller.performFetch()
    } catch {
        fatalError("Unresolved error \(error)")
    }
}

UI is responsive now. But the solution is incorrect...

I guess, we need to place NSFetchedresultController under background thread too. Hence, we do the following modification.

let backgroundContext = persistentContainer.newBackgroundContext()
let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
                                            managedObjectContext: backgroundContext,
                                            sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = fetchedResultsControllerDelegate

backgroundContext.perform {
    // Perform the fetch.
    do {
        // This statement tooks some time to complete if you have a lot of rows.
        try controller.performFetch()
    } catch {
        fatalError("Unresolved error \(error)")
    }
}

The UI is responsiveness during fetching, and data is able to be fetched and shown after some time.

However, if we investigate further by using the launching argument

-com.apple.CoreData.ConcurrencyDebug 1

We will experience the following crash after controller.performFetch() finish executed.

CoreData`+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__]:

May I know, is it ever possible to make UI responsive, when we are using NSFetchedResultController to load a large amount of data (few millions rows as per testing)? Can NSFetchedResultController ever operated under background thread?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • 1
    You shouldn't read all millions of rows at once. Fetch Request has fine tuning properties that allow you to customize properties to fetch, fetch batch size and fetch offset. Use these properties in conjunction with data source prefetching in order to display large amounts of data. – Eugene Dudnyk Apr 03 '21 at 20:14

1 Answers1

1

I think it is possible that your problem is the assignment of the fetchedResultsControllerDelegate by the new controller. This seems to be inferable from where you encounter the multithreading violation.

From the code you provided, it is not clear what kind of object the delegate is, but presumably it manages the UI and thus is on the main thread. You can see who this can potentially lead to a threading problem.

Thanks for posting a link to this question from my answer here https://stackoverflow.com/a/14710475/427083. I still think that it should work for you if you assign the FRC as the datasource of you table once it is finished, and then reloadData.

On a side note: while I find this problem interesting and challenging from an engineering point of view, I doubt that it is good architecture to deal with millions of records directly on a mobile device.

Mundi
  • 79,884
  • 17
  • 117
  • 140