一、UITableView的建構原理
在新聞類,電商類等應用中,應用着大量的圖文混排視圖,在表視圖UITableView中,開發者通常需要在如下代理方法中計算出目前cell填充内容後的高度,之後将其傳回:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//先根據資料源中資料計算高度
CGFloat height = 0;
return height;
}
然而,如果在如上方法中進行列印調試可以發現,heightForRowAtIndexPath方法會重複執行好多次,首先,并且heightForRowAtIndexPath方法的執行機制在不同版本的iOS系統還會有很大不同。以iOS9為例,一行cell要展示在螢幕上,至少要執行5遍TableView的heightForRowAtIndexPath方法:
TableView配置部分:
① 當TableView視圖即将展現在螢幕上時,會把所有行的行高資料進行拉取。
②當TableView在執行setLayoutMargins方法進行自身布局時會把所有行高資料進行拉取。
③TableView在執行layoutSubViews方法進行子視圖布局時會再次把所有行高資料進行拉取。
TableViewCell配置部分:
④當使用cellID進行與TableView綁定的cell擷取時會拉取本行cell的高度資料。
⑤當cell進行layoutSubViews方法進行布局時會再次拉取本行cell的高度資料。
上面列舉的5中拉取cell高度的場景中,TableView配置部分隻會在TableView第一次展現在螢幕上時出現,但是其拉取的是所有行的行高資料,如果表視圖有100行或者更多,這将是一個十分耗費性能的過程。TableViewCell配置部分,隻有當cell将要出現在螢幕上時才會出現,并且隻拉取目前行的行高,這兩種場景會在使用者滑動TableView時不斷被執行,并且根據UITableView的布局cell原理,系統會預設準備目前一屏高度所能容納cell個數加1個cell。
當執行TableView的reloadData方法進行界面重新整理時,系統先會把所有行的行高資料拉取一遍,之後和UITableViewCell配置部分的場景一直,會拉取即将出現在螢幕上的cell的行高資料。
用示意圖形象的表示上述邏輯如下:
通過上面分析,以10行資料的表格視圖為例,若一螢幕可以呈現7行資料(TableView需要準備8行),則在第一次展示TableView視圖時,會執行44次heightForRwoAtIndexPath方法,每次重新整理TableView需要執行24次heightForRwoAtIndexPath方法,如果TableView的行數增加到3位數,則這個方法的執行次數将會十分恐怖👿。
至于為何UITableView在進行配置時也需要拉取所有的行高資料,我猜想其為了進行視圖的一些初始化操作,例如表視圖右側滾動條的寬度和所占比例等。并且,每次拉取高度都從代理方法拉取,而不是存入内部的一個變量屬性中,避免了因為資料源更改時機巧合而産生的界面與預期不一緻的風險。
二、對UITableView可變行高的計算方式進行優化
通過前面的分析,可以了解如果将複雜的計算代碼寫在heightForRowAtIndexPath方法中,代價将是非常慘重的。滑動不流暢,螢幕卡頓很多性能問題都是由于這個原因。對于行高固定的表格視圖,開發者可以直接設定TableView的固定行高,如下:
_tableView.rowHeight = 200;
如果行高是不固定了,則應該想辦法讓heightForRowAtIndexPath方法完成最少的工作,其實最少的工作莫過于拿過一個高度,直接傳回,是以開發者通常會将對應行的行高計算一次後,把值進行儲存,之後在執行heightForRowAtIndexPath方法拉取行高時,直接傳回已經計算過的行高資料,具體如何操作比較靈活,可以對應一個數組屬性,将計算後的行高放入數組中,每次取行高時,檢查數組中是否已經有計算過的行高資料,如果有直接傳回。我個人更傾向将行高資料封裝進cell的資料模型Model中。
通過優化,可以有效的減少重複的高度計算,這也是我原先處理此類問題的主要方式。然而,隻是提高了代碼的性能,對開發者來說,工作量和複雜度有增而無減。在開發中通常會遇到一些十分複雜的界面,而這些界面中cell的高度都是需要通過請求到的資料動态改變的,每個cell都要寫複雜的尺寸計算代碼十分令人心煩。在iOS7之後,系統提供了一種自動計算cell高度的方法,這無論在性能還是工作量上,都完全解放了開發者。
在iOS7系統之後,UITableView類中增加了一個estimatedRowHeight屬性,顧名思義,這個屬性是設定UITableViewCell中的大約行高值。這個值設定之後,開發者無需設定rowHeight屬性,也不需要實作heightForRowAtIndexPath方法,系統會自動根據UITableViewCell中contentView的限制來計算自己的行高。estimatedRowHeight屬性用于TableView進行初始化,其會影響到表格視圖右側滾動條的寬度。cell展現出來時真正的行高并不受這個屬性值的影響。