Closed MoLice closed 1 year ago
背景
考虑以下代码:
- (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
但实际上系统会跳过 numberOfSectionsInTableView,直接询问 numberOfRowsInSection,导致此时我们长度为0的数组被访问了一个下标为0的元素,命中 assert,crash。
原因解释
初看这段代码平平无奇,但这里有个前提,系统的 setDelegate: 会自动 reload 一次,而 setDataSource: 却不会 reload。于是实际的时序是:
setDelegate:
setDataSource:
tableView.dataSource
numberOfSections
setTableHeaderView:
已发布 4.5.0 修复该问题。
背景
考虑以下代码:
预期会先询问
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
,但对业务来说此时数据源是空的,于是越界。