前言
UITableView是我們開發APP最常使用到的控件之一,使用它可以向用戶展示大量的數據和信息,為了把這些數據更好的展示給用戶,tableview的樣式可能會變得非常復雜但美觀。在我們制作出一個好看的tableview的同時,性能問題也逐漸走入了我們的視線。
為了讓tableview的在美觀的同時保持良好的性能,我們需要了解影響tableview性能的原因還有如何優化這些問題。
首先我們要知道,我們滑動時看到界面卡頓是由CPU和GPU開銷共同決定的(參考每日一問02——渲染流程)
1.cell重用
一個tableview中往往會存在很多cell,如果每顯示一個cell都要重新加載,那需要的開銷將會是非常龐大的。蘋果已經為我們提供了最基本的cell重用機制。
//將cell加載到內存
- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
//使用內存中可重用的cell
- (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
2.高度計算
當只有一種高度的情況下,我們可以直接指定cell的高度,這樣是沒有性能影響的。但很多時候會因為內容換行,圖片等不確定因素導致行高不一致。這個時候就需要計算行高或者讓系統計算行高。
>動態計算高度
iOS7后,系統給我們提供了estimatedRowHeight這個屬性,讓我們預估一個cell的高度由系統根據具體的約束計算cell高度。
self.tableView.estimatedRowHeight = 80;
>手動計算高度
很多時候我們會通過cell內控件的布局,約束,文字高度來計算這個cell的具體高度。我們會在代理方法heightForRowAtIndexPath
中返回cell的具體高度。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
return [cell getHeight];
}
這樣做雖然解決了動態高度cell的展示,但會因為每加載一個cell就會進行一次計算導致CPU計算量增加,如果cell中布局非常復雜,那這個消耗是很大的。
>緩存計算高度
對于計算過一次的高度來講,我們不要再次計算,所以我們可以把計算好的高度進行緩存。保存再model里。當需要展示同一個cell時就可以直接使用上一次計算出來的高度。
3.渲染
渲染是由CPU和GPU共同完成的,在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄。就造成了我們看到的卡頓現象。而CPU消耗主要在各種計算上,圖形圖像的計算主要則是靠GPU完成的。所以我們需要讓CPU和GPU利用率盡可能的均衡。
>減少GPU開銷
- 盡量使用不透明的視圖進行布局
當多個透明圖像重疊時,GPU會進行計算,合成出一個顏色,這樣的開銷無疑是巨大的。所以在沒有特殊需求的時候,我們可以設置layer的opaque來關閉合成,使用簡單的拷貝圖層而不考慮這個圖層下面的東西。 - 減少離屏渲染
離屏渲染主要在兩個地方開銷較大:
1.創建新緩沖區,要想進行離屏渲染,首先要創建一個新的緩沖區。
2.上下文切換
我們要知道,CPU和GPU都可能造成離屏渲染。但我們可以讓CPU和GPU負載均衡來保證渲染的性能。例如Core Graphics的所有API都會造成CPU的離屏渲染。
造成離屏渲染的主要方式
- cornerRadius(圓角)
- masks(遮罩)
- shadows(陰影)
- edge antialiasing(抗鋸齒)
- group opacity(不透明)
我們遇到的最多的就是添加圓角造成的離屏渲染,解決方案就是使用CPU來繪制圓角圖片:參考(每日一問06——imageView的圓角優化)
>減少CPU開銷
- 圖片預渲染
當我們使用imageWithContentsOfFile
加載一張圖片時,圖片只是被加載到了內存中并沒有被渲染到UIimageView上,當UIImageView進行渲染時才會在主線程將這個圖片解壓成位圖,這個解壓的過程是一個非常耗時的CPU操作。
解決方案就是:在子線程預先對圖片進行強制解壓,生成可用的位圖(每日一問04-加載圖片對性能的影響)
4.異步
我們都知道UI操作是在主線程執行的,所以我們可以盡可能避免在主線程進行資源加載或對數據進行計算。把這些操作放在子線程中進行再顯示在UI上。最常見的例子就是異步加載網絡圖片并顯示。
5.減少多余的計算
很多時候tableview中會加載很多cell,而屏幕中只能展示其中那么幾個。所以在滑動的過程中,如果我們只是在cellForRowAtIndexPath
中對每一個cell都開線程進行圖片或其他資源的加載,那么消耗將會非常大。
解決方案就是在快速滑動過程中,不執行異步加載,當滾動開始減速的時候才加載顯示在當前屏幕上的cell。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
//如果沒有取到,就初始化
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
}
cell.textLabel.text = @"Name";
BOOL canLoad = !self.tableView.dragging && !_tableView.decelerating;
if (canLoad) {
//開始loaddata,異步加載圖片
NSLog(@"開始加載圖片");
}
return cell;
}
// 滾動停止時,觸發該函數
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
//刷新tableview
// [self.tableView reloadData];
//在此刷新的是屏幕上顯示的cell的內容
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
//獲取到的indexpath為屏幕上的cell的indexpath
[self.tableView reloadRowsAtIndexPaths:visiblePaths withRowAnimation:UITableViewRowAnimationRight];
}
6.使用統一的cell
有的時候,我們界面上會存在許多樣式不同,但布局方式很相似的cell。這時我們可以只編寫一個cell樣式,通過不同需求改變cell樣式。這樣做的好處有兩點
1.減少代碼量,減少Nib文件的數量,統一一個Nib文件定義Cell,容易修改、維護。
2.基于Cell的重用,真正運行時鋪滿屏幕所需的Cell數量大致是固定的,設為N個。所以如果如果只有一種Cell,那就是只有N個Cell的實例;但是如果有M種Cell,那么運行時最多可能會是“M x N = MN”個Cell的實例
在改變cell樣式時,我們應盡量避免動態添加,移除subView,這樣的操作會帶來很多額外的計算。我們應該使用hidden方式直接隱藏和顯示subView,這樣要比重新創建subView性能高得多。
相關文章
提升UITableView性能-復雜頁面的優化
優化UITableViewCell高度計算的那些事
iOS UITableView性能優化
iOS Tableview優化