简体   繁体   中英

UITableView crashes on iOS 9 when endUpdates is called with EXC_BAD_ACCESS

After users upgraded to iOS 9, we've noticed a series of Bad Access ( EXC_BAD_ACCESS ) crashes that don't appear for users who are still on iOS 8. It happens when we call endUpdates on UITableView.

The crash logs include the following reasons:

Selector name found in current argument registers: numberOfRowsInSection:

Selector name found in current argument registers: indexPathForRowAtGlobalRow:

Stack trace #1:

1   UIKit   __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 92
2   UIKit   __46-[UITableView _updateWithItems:updateSupport:]_block_invoke1007 + 224
3   UIKit   -[UITableView _updateWithItems:updateSupport:] + 2556
4   UIKit   -[UITableView _endCellAnimationsWithContext:] + 12892
[...]

Stack trace #2:

1   UIKit   __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 100
2   UIKit   -[UITableViewRowData globalRowForRowAtIndexPath:] + 102
3   UIKit   __46-[UITableView _updateWithItems:updateSupport:]_block_invoke1007 + 182
4   UIKit   -[UITableView _updateWithItems:updateSupport:] + 2300
5   UIKit   -[UITableView _endCellAnimationsWithContext:] + 10552

We are able to repro the issue, but don't have any clue on how to go about fixing it.

It looks like there's a bug in iOS9 when your UITableView has no rows that causes endUpdates to crash with EXC_BAD_ACCESS. To work around this bug, you have to call tableView reloadData before calling beginUpdates.

From the thread that Claudio Redi directed me to: iOS9 iPad UITableView Crash (EXC_BAD_ACCESS) on 1st section insert , I've implemented the following workaround you add before calling [tableView beginUpdates];

if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion >= 9)
{
    // there's a bug in iOS9 when your UITableView has no rows that causes endUpdates to crash with EXC_BAD_ACCESS
    // to work around this bug, you have to call tableView reloadData before calling beginUpdates.

    BOOL shouldReloadData = YES;
    NSInteger numberOfSections = [tableView.dataSource numberOfSectionsInTableView:tableView];
    for (NSInteger section = 0; section < numberOfSections; section++)
    {
        if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] > 0)
        {
            // found a row in current section, do not need to reload data
            shouldReloadData = NO;
            break;
        }
    }

    if (shouldReloadData) 
    {
        [tableView reloadData];
    }
}

I encountered this error when attempting to do more than one of the insert, delete, or reload operations between the beginUpdates() and endUpdates() calls, like so

tableView.beginUpdates()
tableView.deleteRows(at: [deletePaths], with: .fade)
tableView.insertRows(at: [insertPaths], with: .fade)
tableView.endUpdates()

I was able to fix the issue by calling

performBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil)

instead of using the begin and end calls, ex.

self.tableView.performBatchUpdates({
    self.tableView.deleteRows(at: [deletePaths], with: .fade)
    self.tableView.insertRows(at: [insertPaths], with: .fade)
}, completion: nil)

I faced this issue as well, the answer above of calling reloadData() on the table view did resolve the issue, however this was not ideal, as the animation associated with the update was not fluid, due to the reload.

The root of the issue is that I had programmatically called selectRow(at indexPath: IndexPath?, animated: Bool, scrollPosition: UITableView.ScrollPosition) on the table view, for an invalid index path at the time the method was called. (The updates in the table view were to expand/collapse a section to show the rows within or zero rows, I would at times select a row in a collapsed section). This behaviour did not cause a crash when selecting a row for an index path that was not visible in the table view, however when updating the UITableView between beginUpdates( ) and endUpdates() calls after selecting an invalid index path, there would be a crash with EXC_BAD_ACCESS at the endUpdates() call. Adding a check before calling selectRowAt: to ensure the index path was visible/valid, before programmatically selecting resolved the crash without the need to call reloadData()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM