-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Closed
Labels
Description
背景
考虑以下代码:
- (void)viewDidLoad {
[super viewDidLoad];
// ...
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.tableHeaderView = xxx;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionData = data[section];// 假定此时 data 为一个 NSArray 实例,但 count 为 0
return ...;
}预期会先询问 numberOfSectionsInTableView,得知是0,就不会再继续去询问 numberOfRowsInSection,于是没问题。
但实际上系统会跳过 numberOfSectionsInTableView,直接询问 numberOfRowsInSection,导致此时我们长度为0的数组被访问了一个下标为0的元素,命中 assert,crash。
原因解释
初看这段代码平平无奇,但这里有个前提,系统的 setDelegate: 会自动 reload 一次,而 setDataSource: 却不会 reload。于是实际的时序是:
- 先执行
setDelegate:,触发自动 reload,此时tableView.dataSource尚未被赋值,所以numberOfSections为系统默认值1。 - 再执行
setDataSource:,没有自动 reload,tableView 依然认为自己numberOfSections为 1。 setTableHeaderView:会触发 tableView 计算 contentSize,进而触发它询问数据源,由于到此为止依然没人再去 reload,所以 tableView 认为numberOfSections是1,所以直接去询问第一个 section 里的numberOfRowsInSection,但对业务来说此时数据源是空的,于是越界。