I have 2 managed object contexts: (1) created as NSMainQueueConcurrencyType that is used by the UI/main thread and (2) created as NSPrivateQueueConcurrenc
NSFetchedResultsController gets terribly confused if you use an NSFetchRequest when preparing your cell in response to tableView:cellForRowAtIndexPath:. If you don't execute a NSFetchRequest, all is well. However, if you do, it triggers NSFetchedResultsController to perform further change notifications, which does bad things to UITableView.
The workaround for this is to set includesPendingChanges = NO on your NSFetchRequest.
I have opened a radar issue about this -- problem id 14048101 -- with a detailed example and sample app. This bug reproduces on iOS 5.1, 6.0, and 6.1.
In my sample app, I added logging to Xcode's CoreData template to log enter/leave of NSFetchedResultsController delegate methods. When I insert + delete objects on the network context, the logging shows:
01: => (before) mergeChangesFromContextDidSaveNotification 02: => (enter) controllerWillChangeContent count=4 03: <= (leave) controllerWillChangeContent count=4 04: didChangeObject type=1 indexPath=(null) newIndexPath= 2 indexes [0, 0] 05: => (enter) controllerDidChangeContent count=5
At this point, all is good. controllerDidChangeContent: has been called to process the 1 insert, which calls [tableView endUpdates], which calls tableView:cellForRowAtIndexPath:, which calls configureCell:atIndexPath:.
06: => (enter) configure cell at row 0
At this point, configureCell:atIndexPath: creates an NSFetchRequest and calls [self.managedObjectContext executeFetchRequest:error:] -- here begins the badness. Executing this fetch request triggers the processing of the remaining changes in the context (1 delete and 3 updates) before processing of the insert has finished (we entered controllerDidChangeContent: on line #05 and don't leave until line #16).
07: => (enter) controllerWillChangeContent count=5 08: <= (leave) controllerWillChangeContent count=5 09: didChangeObject type=2 indexPath= 2 indexes [0, 4] newIndexPath=(null) 10: didChangeObject type=4 indexPath= 2 indexes [0, 2] newIndexPath=(null) 11: didChangeObject type=4 indexPath= 2 indexes [0, 1] newIndexPath=(null) 12: didChangeObject type=4 indexPath= 2 indexes [0, 3] newIndexPath=(null) 13: => (enter) controllerDidChangeContent count=4
At this point, the framework is making a re-entrant call tocontrollerDidChangeContent:.
14: <= (leave) controllerDidChangeContent count=4 15: <= (leave) configure cell at row 0 16: <= (leave) controllerDidChangeContent count=4 17: <= (after) mergeChangesFromContextDidSaveNotification
At this point, you can see in the UI that: (1) a new cell has been added, (2) 3 cells were updated, and (3) the deleted cell is still visible, which is wrong.
After further action in the UI, I typically get an Assertion failure or message sent to an invalid object exception.
My sample app is available at https://github.com/peymano/CoreDataFetchedResultsController