如圖有一個 TableView,每行顯示這一行是第幾行,現在我希望每按一次 update 按鈕,就動態地在下方加兩行。那么簡單粗暴的做法是 ,更改數據源,然后刷新一下列表:
// tableData = ["0", "1", "2", "3"]
@IBAction func update(_ sender: AnyObject) {
tableData.append("\(tableData.count)")
tableData.append("\(tableData.count)")
tableView.reloadData()
}
用膝蓋想也知道,這會使得前四行沒有被改動的地方也被刷新一遍,帶來了不必要的性能損耗。
好一點的做法是下面這樣的:
// tableData = ["0", "1", "2", "3"]
@IBAction func update(_ sender: AnyObject) {
tableData.append("\(tableData.count)")
tableData.append("\(tableData.count)")
tableView.beginUpdates()
let indexPaths = [IndexPath(row: tableData.count-2, section: 0), IndexPath(row: tableData.count-1, section: 0)]
tableView.insertRows(at: indexPaths, with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
}
與上面相比,這樣做使得 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
方法被少調用了四次。
這里 beginUpdates
和 endUpdates
方法的作用是,將這兩條語句之間的對 tableView 的 insert/delete 操作聚合起來,然后同時更新 UI。鑒于我這里只進行了一次 insert 操作,把這兩條語句去掉也沒事,但是出于規范還是應該寫上,因為假如習慣不寫,下面這樣的代碼會運行時崩潰:
@IBAction func update(_ sender: AnyObject) {
tableData.append("\(tableData.count)")
tableData.append("\(tableData.count)")
// tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: tableData.count-2, section: 0)], with: UITableViewRowAnimation.automatic)
tableView.insertRows(at: [IndexPath(row: tableData.count-1, section: 0)], with: UITableViewRowAnimation.automatic)
// tableView.endUpdates()
}
因為第一次 insert 之后,當前 row 的總數量在 UI 上試圖 4 變成 5,然而數據源是 6,它會檢查使用者對 tableView 的 UI 操作,最后是不是和 numberOfRows 方法獲取的值相對應。
總結
numberOfRows 方法調用: 都只調用一次 numberOfRows 方法
cellForRow 方法調用次數: reloadData 會為當前顯示的所有cell調用這個方法,updates 只會為新增的cell調用這個方法
cellForRow 方法調用時間: reloadData 會在 numberOfRows 方法調用后的某一時間異步調用 cellForRow 方法,updates 會在 numberOfRows 方法調用后馬上調用 cellForRow 方法
reloadData 方法缺陷: 帶來額外的不必要開銷,缺乏動畫
updates 方法缺陷:deleteRows 不會調用 cellForRow 方法,可能導致顯示結果與數據源不一致;需要手動保證 insertRows、deleteRows 之后,row 的數量與 numberOfRows 的結果一致,否則會運行時崩潰
部分文章中沒有寫,總結提到了的部分放在完整 demo 里面了:demo Github 地址