UITableView点击didSelectRow提示NSArray空数组下标越界如何解决?
问题根因确认
你提到的「数据源已修改、UI未完成刷新时用户点击」的场景确实存在:[tableView reloadData] 本质是给UITableView打上待刷新标记,不会同步立即执行cell刷新、行数/section数更新的逻辑,需要等到当前Runloop周期结束才会真正执行刷新操作。你在修改数据源之后、Runloop结束前的这段间隙里,界面展示的还是旧数据对应的索引,若此时用户点击,indexPath 是旧数据的索引,而数据源已经更新为新数据,当新数据源的行数小于旧索引的row值时,就会触发数组越界崩溃,符合你描述的低概率复现特征。
解决方案
可根据业务场景选择以下一种或多种组合方案:
- 访问数据源前增加边界校验(最优先通用方案)
所有需要通过indexPath访问数据源的位置(包括didSelectRow、cell渲染方法等),都先判断索引是否在数据源合法范围内,避免越界:- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // 先校验边界,非法索引直接返回 if (indexPath.section >= [self numberOfSectionsInTableView:tableView] || indexPath.row >= [self tableView:tableView numberOfRowsInSection:indexPath.section]) { return; } id item = [self.datasource objectAtIndex:indexPath.row]; // 后续业务逻辑 } - 调整数据源更新时机,缩短风险间隙
把数据源赋值逻辑从方法开头移到reloadData调用的紧前面,减少数据源修改到触发刷新的间隔时间,降低用户点击的概率:- (void)reloadContent { NSArray *newItems = [self getCurrentItems]; // 先执行所有计算、frame调整、表头设置等非数据源修改逻辑 <Do some Calculations and set tableView frame and other stuff> // 最后再更新数据源,紧接着触发刷新 self.datasource = newItems; [self.tableView reloadData]; } - 刷新过程中临时禁用交互
如果计算逻辑耗时较长,可以在刷新周期内禁用tableView的点击交互,刷新完成后再恢复,可通过CATransaction捕获reloadData的完成回调:- (void)reloadContent { self.tableView.userInteractionEnabled = NO; NSArray *newItems = [self getCurrentItems]; <Do some Calculations and set tableView frame and other stuff> [CATransaction begin]; [CATransaction setCompletionBlock:^{ // 刷新完成后恢复交互 self.tableView.userInteractionEnabled = YES; }]; self.datasource = newItems; [self.tableView reloadData]; [CATransaction commit]; } - 用差异更新代替全量reloadData
如果能拿到新旧数据源的差异(新增/删除/修改的索引),使用insertRowsAtIndexPaths:withRowAnimation:、deleteRowsAtIndexPaths:withRowAnimation:、reloadRowsAtIndexPaths:withRowAnimation:等方法做局部更新,系统会自动保证索引和数据源的一致性,比全量刷新安全性更高,性能也更好。
额外注意:所有对datasource的读写操作、UI相关操作必须保证全部在主线程执行,子线程修改datasource也会触发多线程安全导致的同类崩溃。
内容的提问来源于stack exchange,提问作者Sandeep
相关产品推荐
相关产品推荐

