Skip to content

UITableView 先 setDelegate: 再 setDataSource: 可能引发 crash #1427

@MoLice

Description

@MoLice

背景

考虑以下代码:

- (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。于是实际的时序是:

  1. 先执行 setDelegate:,触发自动 reload,此时 tableView.dataSource 尚未被赋值,所以 numberOfSections 为系统默认值1。
  2. 再执行 setDataSource:,没有自动 reload,tableView 依然认为自己 numberOfSections 为 1。
  3. setTableHeaderView: 会触发 tableView 计算 contentSize,进而触发它询问数据源,由于到此为止依然没人再去 reload,所以 tableView 认为 numberOfSections 是1,所以直接去询问第一个 section 里的 numberOfRowsInSection,但对业务来说此时数据源是空的,于是越界。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions