UITableView的簡單認識
UITableView最核心的思想就是UITableViewCell的重用機制。簡單的理解就是:UITableView只會創建一屏幕(或一屏幕多一點)的UITableViewCell,其他都是從中取出來重用的。每當Cell滑出屏幕時,就會放入到一個集合(或數組)中(這裏就相當於一個重用池),當要顯示某一位置的Cell時,會先去集合(或數組)中取,如果有,就直接拿來顯示;如果沒有,纔會創建。這樣做的好處可想而知,極大的減少了內存的開銷。
知道UITableViewCell的重用原理後,我們來看看UITableView的回調方法。UITableView最主要的兩個回調方法是tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。理想上我們是會認爲UITableView會先調用前者,再調用後者,因爲這和我們創建控件的思路是一樣的,先創建它,再設置它的佈局。但實際上卻並非如此,我們都知道,UITableView是繼承自UIScrollView的,需要先確定它的contentSize及每個Cell的位置,然後纔會把重用的Cell放置到對應的位置。所以事實上,UITableView的回調順序是先多次調用tableView:heightForRowAtIndexPath:以確定contentSize及Cell的位置,然後纔會調用tableView:cellForRowAtIndexPath:,從而來顯示在當前屏幕的Cell。
舉個例子來說:如果現在要顯示100個Cell,當前屏幕顯示5個。那麼刷新(reload)UITableView時,UITableView會先調用100次tableView:heightForRowAtIndexPath:方法,然後調用5次tableView:cellForRowAtIndexPath:方法;滾動屏幕時,每當Cell滾入屏幕,都會調用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法。
什麼是TableView的優化以及爲什麼要優化
CPU(中央處理器)和GPU(圖形處理器) CPU主要從事邏輯計算的一些工作 GPU主要從事圖形處理方面的工作
CPU和GPU的共同點:
都有自己的緩存體系 都有自己的數字和邏輯運算單元 都爲了完成計算任務而設計CPU和GPU的不同點:
CPU的核少但是核內空間非常大 能夠處理複雜的邏輯
GPU核多但是每個核的空間相對較小 故而處理複雜邏輯的空間較少- 針對CPU和GPU的上述不同,面對一個程序系統分配給CPU的往往是較爲複雜的邏輯運算,分配給GPU的通常是圖片等控件的操作
上述不同而導致的結果
- 當程序員爲CPU編程時,傾向於用複雜的邏輯結構優化算法來減少計算任務的時間 Latency
爲GPU編程->利用其能夠處理海量數據的優勢,來提高總的數據的吞吐量 來掩蓋Latency
- 當程序員爲CPU編程時,傾向於用複雜的邏輯結構優化算法來減少計算任務的時間 Latency
爲什麼優化
學術上:平衡CPU和GPU在工作上的壓力,從而正確的使用CPU和GPU的資源,使他們均勻的負載 這樣子使得FPS保持在60幀左右,最終使得用戶體驗更加美好
非學術上:掌握UITableView能夠給面試加分
UITableView的具體優化
1)儘量使用cell的複用
使用cell的複用,可以減少內存的開銷,沒有開闢新的空間,也減少了一些計算量
2)對於不定高的cell 提前將每個cell的高度存入數組,出現一個cell的時候,直接從數組中拿出確切的高度即可,不用臨時計算cell的高度
圖文混排、評論
對於固定高的cell和不定高的cell同樣適用
3)涉及網絡請求加載數據在UITableView滑動結束的時候在進行加載數據(渲染)避免卡頓
- UITableView繼承自UIScrollView,繼承了後者的方法
//滑動結束的方法
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
//減速結束之後的方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
- if(self.tableView.dragging==NO&&self.tableView.decelerating==NO)在tableView必須實現的二個方法中(加載cell的方法中)將數據的渲染寫在上述if語句中
4)對於tableView的自定義控件 尤其是UIImageView,儘量減少使用圓角,陰影等layer屬性,儘量減少使用alpha(透明度)來設置透明度,(在項目開發中,讓UI設計師設計原圖就是帶圓角的圖) 陰影,圓角這些layer效果都是在GPU中完成的
當多個視圖重疊時,GPU會進行合成渲染,而渲染的最慢的操作就是混合,因此當視圖結構太過複雜,就會大量消耗GPU的資源,所以當一個空間本身是不透明,注意設定alpha爲1,這樣可以避免無用的alpha通道合成,降低GPU的負載
對控件設置cornerRadius後對其進行clip或mask操作時 會導致offscreenrendering這個也是在GPU中進行的 如果在滑動時 圓角對象太多 回到GPU的負載大增幅。
這時我們可以設置layer的shouldRasterize屬性爲YES,可以將負載轉移給CPU 更徹底的是直接使用帶圓角的原圖
5)儘量使用懶加載
又稱爲延遲加載 實際上是重寫某個對象的getter方法 原理:程序一開始並不對它進行初始化 而是在用到他的時候 才爲他開闢內存供它使用
好處:
不必將創建的對象的代碼全部寫在ViewDidLoad中,代碼可讀性強
每個控件的getter方法,分別負責各自的實例化處理,代碼彼此之間獨立性強 鬆耦合
6)減少返回給的cell裏面的處理邏輯和處理時間
7)設置每個cell的opaque屬性 —-面試亮點
opaque意思是不透明的 渾濁的 有YES和NO二個結果
alpha 透明度
如果控件本身不透明,我們設置opaque爲YES
8)分段加載數據
設置分頁加載數據 也就是上拉刷新和下拉加載
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ContTableCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ContacterTableCell"];
if (!cell) {
cell = (ContTableCell *)[[[NSBundle mainBundle] loadNibNamed:@"ContacterTableCell" owner:self options:nil] lastObject];
}
NSDictionary *dict = self.dataList[indexPath.row];
[cell setContentInfo:dict];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
/*
這樣寫,在Cell賦值內容的時候,會根據內容設置佈局,當然也就可以知道Cell的高度,如果100行,那就會調用100+頁面Cell個數次tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法,而我們對Cell的處理操作,都是在這個方法裏的!什麼賦值、佈局等等。開銷自然很大,所以改進代碼。
*/
/*
//改寫後代碼
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dict = self.dataList[indexPath.row];
return [ContacterTableCell cellHeightOfInfo:dict];
}
*/
tableview的優化主要是把賦值和計算佈局分離。這樣讓tableView:cellForRowAtIndexPath:方法只負責賦值,tableView:heightForRowAtIndexPath:方法只負責計算高度。兩個方法儘可能的各司其職,不要重疊代碼!兩者都需要儘可能的簡單易算。我們可以在獲得數據後,直接先根據數據源計算出對應的佈局,並緩存到數據源中,這樣在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都計算了。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dict = self.dataList[indexPath.row];
CGRect rect = [dict[@"frame"] CGRectValue];
return rect.frame.height;
}
UITableView的優化主要從三個方面入手:
- 提前計算並緩存好高度(佈局),因爲heightForRowAtIndexPath:是調用最頻繁的方法;
- 異步繪製,遇到複雜界面,遇到性能瓶頸時,可能就是突破口;
- 滑動時按需加載,這個在大量圖片展示,網絡加載的時候很管用!(SDWebImage已經實現異步加載,配合這條性能槓槓的)。
到這裏,優化後的代碼基本能滿足簡單的界面!如果需要實現較爲複雜的圖文混排,還需要進一步優化。
如果項目是用的Xib、Storyboard,並需要優化UITableView的話,可以看看sunnyxx大神的方案:http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/