TableView的優化

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和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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章