UITableViewDiffableDataSource apply snapshot crashes on iOS 17

Hello all, We got app crashes on dataSource.apply(snapshot, animatingDifferences: false) after refactoring our tableView code to UITableViewDiffableDataSource. This doesn't happen every time.

    DispatchQueue.main.async { [weak self] in
        guard let self = self else { return }
        var snapshot = getCurrentSnapshot()
        snapshot.deleteItems([...])
        snapshot.appendItems([...], toSection: section)
        dataSource.apply(snapshot, animatingDifferences: false)            
    }

And the controller has deallocated before it.

Crash log:

controller (0x110a64800): deallocated
*** Assertion failure in NSInteger _UITableViewRowDataNumberOfRowsInSection(UITableViewRowData *__unsafe_unretained, NSInteger)(), UITableViewRowData.m:1761
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Requested the number of rows for section (4) which is out of bounds.'
*** First throw call stack:
(
	0   CoreFoundation                      0x0000000180491128 __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x000000018008412c objc_exception_throw + 56
	2   Foundation                          0x0000000180d11770 -[NSMutableDictionary(NSMutableDictionary) classForCoder] + 0
	3   UIKitCore                           0x0000000185538ad0 -[UITableViewRowData numberOfRowsInSection:] + 260
	4   UIKitCore                           0x00000001855052f0 -[UITableView _shouldDrawSeparatorAtBottomOfSectionForCellAtIndexPath:] + 152
	5   UIKitCore                           0x0000000185504dbc -[UITableView _updateSeparatorStateForCell:atIndexPath:] + 100
	6   UIKitCore                           0x0000000185504cf0 -[UITableView _updateSeparatorStateForVisibleCells] + 188
	7   UIKitCore                           0x00000001854f4bd0 -[UITableView _updateAnimationDidStopWithOldVisibleViews:finished:context:] + 1108
	8   UIKitCore                           0x00000001854f2630 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke_4 + 36
	9   UIKitCore                           0x00000001857fcb1c __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__ + 28
	10  UIKitCore                           0x00000001857fce24 -[UIViewAnimationBlockDelegate _sendDeferredCompletion:] + 100
	11  libdispatch.dylib                   0x000000010e5100f0 _dispatch_call_block_and_release + 24
	12  libdispatch.dylib                   0x000000010e51193c _dispatch_client_callout + 16
	13  libdispatch.dylib                   0x000000010e5215e4 _dispatch_main_queue_drain + 1228
	14  libdispatch.dylib                   0x000000010e521108 _dispatch_main_queue_callback_4CF + 40
	15  CoreFoundation                      0x00000001803f1a30 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
	16  CoreFoundation                      0x00000001803ec148 __CFRunLoopRun + 1936
	17  CoreFoundation                      0x00000001803eb5a4 CFRunLoopRunSpecific + 572
	18  GraphicsServices                    0x000000018e9fbae4 GSEventRunModal + 160
	19  UIKitCore                           0x00000001852f02e4 -[UIApplication _run] + 868
	20  UIKitCore                           0x00000001852f3f5c UIApplicationMain + 124
	21  appName             0x0000000103e8b0dc $s22appNameySpySpys4Int8VGGXEfU_ + 192
	22  appName              0x0000000103e8b480 _swift_se0333_UnsafeMutablePointer_withMemoryRebound + 900
	23  appName              0x0000000103e8af30 main + 1320
	24  dyld                                0x000000010e409544 start_sim + 20
	25  ???                                 0x000000010e61a0e0 0x0 + 4536246496
	26  ???                                 0xca54000000000000 0x0 + 14579277893705138176
)
libc++abi: terminating due to uncaught exception of type NSException

Where is DispatchQueue.main.async being called from? What object is self (a view controller or some other object)?

And the controller has deallocated before it.

Are you saying that the view controller has been deallocated before the table view it displays? Sounds like you might have a memory leak. You can use the memory graph debugger to try to reproduce and/or Instruments.

I'd check for unintentional strong references to the table view. References in blocks is a place I'd look for first but it could be something else. It's easy to create a retain cycle using blocked based APIs. Swipe gesture action blocks, context menu action blocks, diffable datasource provider blocks, or your own blocks.

Why not call the async variant of apply?

UITableViewDiffableDataSource apply snapshot crashes on iOS 17
 
 
Q