3lvis / DATASource

Core Data's NSFetchedResultsController wrapper for UITableView and UICollectionView
Other
106 stars 27 forks source link

NSInternalInconsistencyException at controllerDidChangeContent (DATASource+NSFetchedResultsControllerDelegate.swift:137) #120

Closed yonat closed 4 years ago

yonat commented 4 years ago

Hi @3lvis and thanks for such a useful library!

I have a crash that occurs rarely, I think it's related for an object moving from one section to another, causing a certain section to be removed or inserted.

I have 4 Session objects, sorted to sections by phase, and when one of them changes phase I get a crash:

SESSION BEFORE AFTER
Session/p3 phase = 2 phase = 2
Session/p39 phase = 1 phase = 1
Session/p40 phase = 3 phase = 3
Session/p41 phase = 0 phase = 1

Any idea what might be going on?

The errors:

Assertion failure in -[UITableView _Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3899.22.15/UITableView.m:2401 [error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) CoreData: fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) [error] CoreData: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null) Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

*** First throw call stack:

    0   CoreFoundation                      0x00007fff23baa1ee __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff50864b20 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff23ba9f68 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff25614de9 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x00007fff4773f1d9 -[UITableView _Bug_Detected_In_Client_Of_UITableView_Invalid_Number_Of_Rows_In_Section:] + 193
    5   UIKitCore                           0x00007fff4773ea92 -[UITableView _endCellAnimationsWithContext:] + 16657
    6   UIKitCore                           0x00007fff47758a89 -[UITableView endUpdatesWithContext:] + 112
    7   DATASource                          0x000000010e5c754c $s10DATASourceAAC26controllerDidChangeContentyySo26NSFetchedResultsControllerCySo20NSFetchRequestResult_pGF + 780
    8   DATASource                          0x000000010e5ca744 $s10DATASourceAAC26controllerDidChangeContentyySo26NSFetchedResultsControllerCySo20NSFetchRequestResult_pGFTo + 68
    9   CoreData                            0x00007fff238ed472 __82-[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:]_block_invoke + 7591
    10  CoreData                            0x00007fff23776aa3 developerSubmittedBlockToNSManagedObjectContextPerform + 154
    11  CoreData                            0x00007fff2377698a -[NSManagedObjectContext performBlockAndWait:] + 197
    12  CoreData                            0x00007fff238eb6b9 -[NSFetchedResultsController(PrivateMethods) _core_managedObjectContextDidChange:] + 105
    13  CoreFoundation                      0x00007fff23ad43fc __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
    14  CoreFoundation                      0x00007fff23ad3875 _CFXRegistrationPost1 + 421
    15  CoreFoundation                      0x00007fff23ad35e1 ___CFXNotificationPost_block_invoke + 193
    16  CoreFoundation                      0x00007fff23bd1a33 -[_CFXNotificationRegistrar find:object:observer:enumerator:] + 1811
    17  CoreFoundation                      0x00007fff23ad2f36 _CFXNotificationPost + 950
    18  Foundation                          0x00007fff25676cb5 -[NSNotificationCenter postNotificationName:object:userInfo:] + 59
    19  CoreData                            0x00007fff237633a6 -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 541
    20  CoreData                            0x00007fff23806e56 -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:deletions:updates:refreshes:deferrals:wasMerge:] + 1557
    21  CoreData                            0x00007fff2375de9b -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 1217
    22  CoreData                            0x00007fff237611a3 -[NSManagedObjectContext save:] + 367
yonat commented 4 years ago

It seems controller(:, didChange:, atSectionIndex:, for:) doesn't get called at all. Only controller(:, didChange:, at:, for:, newIndexPath:) gets called for each object with type == .update .

So maybe it would be a good idea to check if newIndexPath != indexPath ?

yonat commented 4 years ago

I created a minimal project that reproduces the crash at https://github.com/yonat/DATASourceSectionBug .

The crash-causing code is at https://github.com/yonat/DATASourceSectionBug/blob/d1977b5fe68f33bf02e9c476433f9c5ca3731c5c/SectionBug/AppDelegate.swift#L26

yonat commented 4 years ago

The crash is avoided if I remove the workaround here: https://github.com/3lvis/DATASource/blob/cf5e9a83264ea00ab64cb7ef44c70aa7d1666aad/Source/DATASource%2BNSFetchedResultsControllerDelegate.swift#L82

ottne commented 4 years ago

I'm seeing similar problems in one of our apps. But so far I have not been able to reproduce the crash. We updated from version 7.2.0 to 7.4.0 and are still seeing crashes. I wonder if the problem is related to the one described in this issue at all.

This is what I found in the Firebase traces:

Fatal Exception: NSInternalInconsistencyException Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).

3lvis commented 4 years ago

Just wanted to share that in my new projects, I've been moving away from Core Data and it has improved the clarity of my code.