Crashing when trying to use NSFetchedResultsController fetchedObjects

As of iOS 9, and in an old iOS 8.3 beta seed (12F5047f), but not the GM version of iOS 8.3, I often see crashes when using NSFetchedResultsController fetchedObjects. For example, when creating an NSSet from them or filtering them with an NSPredicate it often crashes. In both of these cases the code is running from viewDidDisappear but that's probably just a coincidence.


My Core Data stack is set up to have a main queue context and private queue context both connected directly to the persistent store coordinator, with changes from the private queue context being merged with mergeChangesFromContextDidSaveNotification (plus a workaround for NSFetchedResultsController when using this setup).


Has anyone else been seeing this behavior? I can't get it to reproduce with the debugger attached but it happens frequently with regular usage.

Replies

Yes I have a very similar problem

The same problem here. Accessing `fetchedObjects` is causeing a crash with the following stack trace:


* thread #1: tid = 0x23ea0, 0x0000000199581efc libobjc.A.dylib`objc_msgSend + 28, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x6337306253566120)
    frame #0: 0x0000000199581efc libobjc.A.dylib`objc_msgSend + 28
    frame #1: 0x0000000184c566fc CoreFoundation`+[__NSArrayI __new:::] + 100
  * frame #2: 0x0000000184c53b70 CoreFoundation`-[NSArray initWithArray:range:copyItems:] + 400
    frame #3: 0x0000000102183698 libswiftFoundation.dylib`static ext.Foundation.Swift.Array._forceBridgeFromObjectiveC <A>(Swift.Array<A>.Type)(ObjectiveC.NSArray, result : inout Swift.Optional<Swift.Array<A>>) -> () + 140
    frame #4: 0x00000001021835e4 libswiftFoundation.dylib`Foundation._convertNSArrayToArray <A>(ObjectiveC.NSArray) -> Swift.Array<A> + 48
    frame #5: 0x00000001000ac650 MyApp`MyApp.FilesViewController.tableView (tableView=0x0000000102a08800, section=, self=0x000000010ca44fb0)(ObjectiveC.UITableView, numberOfRowsInSection : Swift.Int) -> Swift.Int + 260 at FilesViewController.swift:215
    frame #6: 0x00000001000ac780 MyApp`@objc MyApp.FilesViewController.tableView (MyApp.FilesViewController)(ObjectiveC.UITableView, numberOfRowsInSection : Swift.Int) -> Swift.Int + 76 at FilesViewController.swift:0
    frame #7: 0x000000018a461c98 UIKit`-[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2256
    frame #8: 0x000000018a58c724 UIKit`-[UITableViewRowData ensureAllSectionsAreValid] + 184
    frame #9: 0x000000018a56eb80 UIKit`-[UITableView _endCellAnimationsWithContext:] + 444
    frame #10: 0x00000001000add04 MyApp`MyApp.FilesViewController.controllerDidChangeContent (controller=0x00000001078c4620, self=0x000000010ca44fb0)(ObjectiveC.NSFetchedResultsController) -> () + 264 at FilesViewController.swift:278
    frame #11: 0x00000001000add8c MyApp`@objc MyApp.FilesViewController.controllerDidChangeContent (MyApp.FilesViewController)(ObjectiveC.NSFetchedResultsController) -> () + 68 at FilesViewController.swift:0
    frame #12: 0x0000000184b341c8 CoreData`__77-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:]_block_invoke + 3976
    frame #13: 0x0000000184aaaa48 CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 196
    frame #14: 0x0000000184aaa930 CoreData`-[NSManagedObjectContext performBlockAndWait:] + 248
    frame #15: 0x0000000184a24c74 CoreData`-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 116
    frame #16: 0x0000000184d127e8 CoreFoundation`__CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
    frame #17: 0x0000000184d12008 CoreFoundation`_CFXRegistrationPost + 396
    frame #18: 0x0000000184d11d88 CoreFoundation`___CFXNotificationPost_block_invoke + 60
    frame #19: 0x0000000184d7461c CoreFoundation`-[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1508
    frame #20: 0x0000000184c4f264 CoreFoundation`_CFXNotificationPost + 352
    frame #21: 0x0000000185c0277c Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 68
    frame #22: 0x0000000184a24bd8 CoreData`-[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 80
    frame #23: 0x0000000184aaa2f0 CoreData`-[NSManagedObjectContext _mergeChangesFromDidSaveDictionary:usingObjectIDs:] + 3132
    frame #24: 0x0000000184aaa5e0 CoreData`-[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] + 500

There might be a workaround for this isssue. Instead of using `fetchedObjects`, iterate through `sections` and `objectAtIndexPath` to get all the objects. It requires more effort but it seems it doesn't make the app crash.

Yeah, we found a similar workaround. We just enumerated over the values in fetchedObjects and added each element to a new array. Seems strange but it isn't crashing now. Hopefully it'll be fixed in the GM.

Well, the workaround didn't fix all of our problems. In another part of our code, calling indexPathForObject will indirectly call our fetchedObjects workaround, which then crashes when enumerating over the objects to add them to another array. I'm guessing ARC's retain or release is being called on deallocated managed objects. (Edit: Yes, it was retain being called inside of -[NSMutableArray addObject:] after adding it to its internal storage.)


Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libobjc.A.dylib                0x000000019a3c1bd0 objc_msgSend + 16
1   MyApp                          0x000000010081d9dc -[NSFetchedResultsController(iOS9Workaround) iOS9__fetchedObjects] (NSFRC+iOS9Workaround.m:63)
2   CoreData                       0x0000000185636c0c -[_NSDefaultSectionInfo indexOfObject:] + 92
3   CoreData                       0x0000000185636b3c -[NSFetchedResultsController indexPathForObject:] + 180

We're trying another workaround, similar to yours, where we create a new array by iterating over fetchedObjects using indexes, not its enumerator object with a "for in" loop.


Curiously, while "po"-ing various objects in the debugger to try and diagnose the problem, we noticed that the managed object returned from fetchedObjects changed to a valid object. (It was at a different address than the original one, which wasn't a valid object anymore.)


However, we found a somewhat more promising solution: we changed our Core Data stack to use parent contexts (not mergeChangesFromContextDidSaveNotifcation) and we couldn't reproduce the crash afterwards. However, we'd prefer not to use parent context because of performance issues in previous iOS versions. (This may be improved in iOS 9, though.)

Unfortunately this problem is still present in the GM. 😟

Using the enumerateUsingBlock APIs seems to be good enough to workaround this bug. It seems that the bug lies in the underlying array's implementation of NSFastEnumeration.


Also, this crash happens in the release versions of iOS >= 8.3 as well, we've found.

I still get random EXC_BAD_ACCESS crashing in iOS 14.7 using fetchedObjects or even using object(at:).

    let batch = (beforeUpdate..<afterUpdate).map { controller.object(at: .init(row: $0, section: 0)) }

The weird thing right after the crash in the debugger I can access the object at that row without problem:

(lldb) p controller.fetchedObjects![$0]
(NSFetchRequestResult) $R9 = (object = 0x0000000280fac9b0)
(lldb) p $0
(Int) $R7 = 200
(lldb) p controller.object(at: .init(row: $0, section: 0)) as? NSManagedObject
(NSManagedObject?) $R6 = 0x0000000280fac9b0 {
  baseCDContact@0 = {

The good news is that this issue seems to be fixed in the iOS 15 beta...