Have you ever encountered NSFetchedResultsController not able to properly "section" based on sectionNameKeyPath, randomly?

This problem has buzzed me quite a while. So far, I still haven't founded a good solution.

Currently, I have an entity with a Bool column named pinned. I use it as the sectionNameKeyPath for NSFetchedResultsController.

So, my UICollectionView will always have 1 sections (All pinned = true, or All pinned = false), or 2 sections (Some pinned = true, and some pinned = false)

When I toggle the pinned value from true to false, or false to true, I expect FRC shall fire a "move" callback. (Due to item has moved to a new section) This happen most of the time.

However, sometimes, randomly, instead of firing "move" callback, FRC will fire "update" callback.

When such incident happen, I will exam the content of fetchedResultsController.sections. Then, I will notice the entity item stays in wrong section.

My FRC looks pretty straightforward.

Code Block
lazy var fetchedResultsController: NSFetchedResultsController<NSPlainNote> = {
// Create a fetch request for the Quake entity sorted by time.
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "pinned", ascending: false)
]
// Create a fetched results controller and set its fetch request, context, and delegate.
let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: CoreDataStack.INSTANCE.persistentContainer.viewContext,
sectionNameKeyPath: "pinned",
cacheName: nil
)
controller.delegate = fetchedResultsControllerDelegate
// Perform the fetch.
do {
try controller.performFetch()
} catch {
fatalError("Unresolved error \(error)")
}
return controller
}()

This is how I update the pinned column using background thread.

Code Block
func updatePinned(_ objectID: NSManagedObjectID, _ pinned: Bool) {
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
let nsPlainNote = try! backgroundContext.existingObject(with: objectID) as! NSPlainNote
nsPlainNote.pinned = pinned
RepositoryUtils.saveContextIfPossible(backgroundContext)
}
}


I am not really sure, whether this can caused by my background thread.

As, if I replace the backgroundContext with viewContext, I haven't observed the random problem so far.

But, even so, I am not confident to conclude using backgroundContext is the culprit to this problem.
The setup of my background thread is also pretty straightforward. I cannot see how it can went wrong.

Code Block
class CoreDataStack {
public static let INSTANCE = CoreDataStack()
private init() {
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "***")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// TODO:
container.viewContext.automaticallyMergesChangesFromParent = true
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
// TODO:
//backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//backgroundContext.undoManager = nil
return backgroundContext
}()
}


I was wondering, have anyone of you encounter similar problem when trying to utilize sectionNameKeyPath or FRC? Do you know what? Do you have any workaround/ solution for that?

Thank you!




The documentation states:

Changes are not reflected until after the controller’s managed object context has received a processPendingChanges() message. Therefore, if you change the value of a managed object’s attribute so that its location in a fetched results controller’s results set would change, its index as reported by the controller would typically not change until the end of the current event cycle (when processPendingChanges() is invoked).

Maybe what’s happening is just that. And on the view context pending changes are just processed sooner. You could hook into that method to check.

https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller
Have you ever encountered NSFetchedResultsController not able to properly "section" based on sectionNameKeyPath, randomly?
 
 
Q