Tencent / QMUI_iOS

QMUI iOS——致力于提高项目 UI 开发效率的解决方案
http://qmuiteam.com/ios
Other
7.08k stars 1.38k forks source link

[UIKit Bug] iOS 11 及以上,关闭 estimated height 的 tableView 可能出现数据错乱引发 crash #1243

Closed MoLice closed 3 years ago

MoLice commented 3 years ago

Bug 表现 image

如何重现

self.tableView = [UITableView alloc] init];

// 1. 在 setDataSource、关闭 estimated 之前先设置 tableHeaderView/tableFooterView(常见的例如 tableHeaderView = searchBar)
self.tableView.tableHeaderView = UIView.new;

// 2. 关闭 estimated(不管是操作 rowHeight 还是 sectionHeaderHeight、sectionFooterHeight 均可)
self.tableView.estimatedRowHeight = 0;
self.tableView.rowHeight = 56;

// 3. setDataSource
self.tableView.delegate = self;
self.tableView.dataSource = self;

// 4. 主动调用 tableView reloadData
[self.tableView reloadData];

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section {
    id a = self.dataSource[section]; // 5.  这个方法会错误地被访问(此时不存在 section,但却调用了,参数 section 传进来0,导致越界)
    return UITableViewAutomaticDimension;
}

可重现的 Demo: TestTableViewEstimated.zip

其他信息

MoLice commented 3 years ago

Bug 原因分析:

理论上正确的时序应该是 tableView 先询问 - [UITableViewDataSource numberOfSectionInTableView:],获知列表是 0 个 section,于是就不会再询问 estimatedHeightForHeaderInSection 之类的方法。

但在 issue 描述的这种特定场景里,系统内部可能状态错乱,导致先触发了 estimatedHeightForHeaderInSection(不仅限于它,也可能是 estimatedHeightForFooterInSection、heightForRow、...),传的参数是 section = 0(UITableView 默认的 numberOfSection 为 1)但此时业务的 dataSource 数组是空的,所以访问 dataSource[0] 就出现越界的 crash。

系统在这种场景下为什么会出现这种错误的时序,原因未知,目前 QMUI 的修复方式是在 tableView 的 reloadData 第一次执行前手动检查 delegate 返回的数据和 tableView 内部的数据是否一致,如果不一致则手动刷新一次,从而规避这种问题。

MoLice commented 3 years ago

已发布 4.3.0 修复该问题。