Exception… Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed

On iPadOS and iOS13 on a call to visibleCells on a UITableView ( on code which works fine on iOS9 to iOS12 ) I get an exception with the message


Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed.


Searching on the web gives me no hits for this exact message, so I suspect the message itself is new or changed.



Now the general gist of the message is fairly clear, but the specifics and but how one is expected to avoid this in a multithreaded application are far from clear.


For example ...what sort of changes qualify,


For example ... the user may rotate the device, a redisplay may be caused by incoming data. Are these now to be expected to crash your application?


Maybe there is a new threading model / usage requirement that I don't know about, yet.


Synchronising on the UITableView itself doesn't help.


What am I missing



And Yes, I can avoid the problem by catching the error and backing off until I succeed. But the hit on performance is eyewatering. And it's horrible.

Accepted Reply

This is a new exception in iOS 13 that UITableView will raise in order to prevent and proactively alert you of a situation that would previously cause undefined behavior and a variety of strange, seemingly unrelated, and hard-to-debug issues (including crashes).


What is happening here is that UITableView is in the middle of asking its dataSource to return a cell for each visible row and is configuring the properties of the returned cells so they can be displayed. And in the middle of this updating -- most likely inside a callback from the table view itself about a specific row such as tableView(_:cellForRowAt:) tableView(_:canEditRowAt:), etc -- your code is asking the table view to return the visibleCells. This is obviously problematic, because UITableView is right in the middle of preparing those cells, so it cannot possibly return a meaningful answer.


The fix for this is to look at where you are calling visibleCells in the backtrace when this exception is raised, and then do one of two things:


Option 1: Move the usage of visibleCells to a better place, so that you aren't asking for the visibleCells from some place that is called during the process of creating/configuring/updating those same cells. A great place to ask for the visible cells is after the table view lays out, so for example if the table view is the view of a view controller you can use viewDidLayoutSubviews(), or in a subclass of UITableView do it after calling super.layoutSubviews().


Option 2: Depending on what you're actually trying to do, you might be able to skip using visibleCells altogether. For example, you might be able to leverage the callbacks tableView(_:willDisplay:forRowAt:) and tableView(_:didEndDisplaying:forRowAt:) to track when cells are visible instead.


Another place you may hit this is if you are using Key-Value Observation to observe the table view's contentOffset directly, and are asking for the visibleCells every time that changes. You should use the UIScrollView delegate method scrollViewDidScroll(_:) instead, or possibly a different approach altogether (such as the suggestion in Option 2 above).


If you are hitting this exception and you think you are requesting the visibleCells from a location that should be valid/allowed, please share the backtrace when you hit this exception and details about what you're trying to do.

Replies

Hey tylerf!

Been experiencing crash all day with exception. Please help


2019-10-03 13:51:21.910027-0400 Proteus[69427:3583092] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Requested the number of rows for section (1) which is out of bounds.'

*** First throw call stack:

(

0 CoreFoundation 0x00007fff23b98bde __exceptionPreprocess + 350

1 libobjc.A.dylib 0x00007fff503b5b20 objc_exception_throw + 48

2 CoreFoundation 0x00007fff23b98958 +[NSException raise:format:arguments:] + 88

3 Foundation 0x00007fff255eb7be -[NSAssertionHandler handleFailureInFunction:file:lineNumber:description:] + 166

4 UIKitCore 0x00007fff47381d87 -[UITableViewRowData numberOfRowsInSection:] + 436

5 Proteus 0x000000010952959f $s7Proteus26ElasticSearchTableViewCellC15prepareForReuseyyF + 127

6 Proteus 0x0000000109529658 $s7Proteus26ElasticSearchTableViewCellC15prepareForReuseyyFTo + 24

7 UIKitCore 0x00007fff47346d68 -[UITableView _dequeueReusableViewOfType:withIdentifier:] + 181

8 UIKitCore 0x00007fff473473fa -[UITableView _dequeueReusableCellWithIdentifier:forIndexPath:usingPresentationValues:] + 153

9 UIKitCore 0x00007fff4734732d -[UITableView dequeueReusableCellWithIdentifier:forIndexPath:] + 91

10 Proteus 0x0000000109331cac $s7Proteus17ElasticControllerC9tableView_12cellForRowAtSo07UITableE4CellCSo0jE0C_10Foundation9IndexPathVtF + 2700

11 Proteus 0x0000000109332100 $s7Proteus17ElasticControllerC9tableView_12cellForRowAtSo07UITableE4CellCSo0jE0C_10Foundation9IndexPathVtFTo + 128

12 UIKitCore 0x00007fff47360e4b -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 787

13 UIKitCore 0x00007fff4732a3c6 -[UITableView _updateVisibleCellsNow:] + 3081

14 UIKitCore 0x00007fff4734a318 -[UITableView layoutSubviews] + 163

15 UIKitCore 0x00007fff47636722 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478

16 QuartzCore 0x00007fff2b030ef9 -[CALayer layoutSublayers] + 255

17 QuartzCore 0x00007fff2b0358ff _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 517

18 QuartzCore 0x00007fff2b041fe4 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 80

19 QuartzCore 0x00007fff2af8a4a8 _ZN2CA7Context18commit_transactionEPNS_11TransactionEd + 324

20 QuartzCore 0x00007fff2afbfab3 _ZN2CA11Transaction6commitEv + 643

21 QuartzCore 0x00007fff2afc041a _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 76

22 CoreFoundation 0x00007fff23afaeb7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23

23 CoreFoundation 0x00007fff23af594e __CFRunLoopDoObservers + 430

24 CoreFoundation 0x00007fff23af5fca __CFRunLoopRun + 1514

25 CoreFoundation 0x00007fff23af56b6 CFRunLoopRunSpecific + 438

26 GraphicsServices 0x00007fff3815cbb0 GSEventRunModal + 65

27 UIKitCore 0x00007fff47162a67 UIApplicationMain + 1621

28 Proteus 0x00000001092f542a main + 58

29 libdyld.dylib 0x00007fff5123bcf5 start + 1

)

libc++abi.dylib: terminating with uncaught exception of type NSException

(lldb)

What're the guidelines for dealing with this in a tabbed application w/ an active NSFetchedResultsController? We have a Notifications tab that periodically fetches data in the background, which can trigger the tab's NSFetchedResultsController even when that tab isn't currently selected. It seems that UITableView.beginUpdates(), invoked via my NSFetchedResultsControllerDelegate callback, throws this warning.

I had this problem in two places.


I used the suggestion in the console message to create a symbolic break on UITableViewAlertForCellForRowAtIndexPathAccessDuringUpdate and then clicked on the white bolded references in the (backtrace window, which was the breakpoint navigator before running the simulator). These took me right to the line in my code with the issue.


I missed this 'click on the white bolded line in backtrace' previous attempts at debugging this and like this issue.


In one case, in the viewForHeaderInSection func, I had some UI element based on the presence of a row for that section or not. I changed that to some other attribute not based on the related formed/forming cell.


In the other case, I had similarly put a condition in the canEditRowAt that would test if the `tableView.cellForRow(at:)` was of a certain class. Unhappy form. Replaced it with a condition based on a non-UI element and issue... gone.


Nowhere did I use visibleCells explicitly, but I did analyze a visible cell. The point: don't be so literal in interpreting a console message.