UITableView的優化技巧

作者:Love@YR
鏈接:http://blog.csdn.net/jingqiu880905/article/details/51920117
請尊重原創,謝謝!

UITableView的優化是個難點也是個痛點,下面列出各路大神總結的優化技巧。

首先,基礎篇:

cell繪製方面cellForRowAtIndexPath:

  • cell重用 reuseIdentifier
    這個可以看下上一篇文章,講解了不同情況下alloc了多少個cell就比較清楚了。不用reuse你內存受不了。
    (也應該在section header和footer中使用reuseIdentifier)

  • 統一設計cell
    cell的Prototype最好高度抽象統一,越少越好,因爲不同的Prototype有其對應的cell重用池,創建的cell種類越多,重用效率也就越低。所以能用一種就用一種。

  • 減少視圖層級關係,減少subview的數量
    儘量少用addView給cell動態添加view,可以初始化時就添加,然後通過hide來控制是否顯示。

  • 儘量使用opaque
    儘量使cell的contentView所有的subview都設置opaque,包括Cell自身。儘量少用或不用透明圖層。

  • 繪製方法中儘量不要處理過多業務邏輯

高度計算方面heightForRowAtIndexPath:

  • row 的高度都一定的情況
    刪除代理中的heightForRowAtIndexPath: 方法,設置 tableView的rowHeight屬性,sectionHeaderHeight footer也同樣的道理。

其他:

  • cell數據資源緩存
    一般大家也都會直接用個dictionary或者數組或者model來存下需要顯示的數據源,不大可能會到用的時候再去讀plist啊,拿數據庫裏的之類。沒啥好說的。

  • UI的尺寸和行高緩存,用 “空間換時間”
    正常情況計算行高放在heightForRowAtIndexPath,但如果這個計算很複雜,時間不固定,有時短有時長,你再一reloaddata,或者insertrow,deleterow那就很煩了。
    所以將計算行高的時間(當然連同各UI的尺寸計算,你只有算出來所有子view高寬度才能確定cell高度)提前到從服務器拿到數據的時候就開始,計算完了將cell高度,各行各ui的frame一併寫回數據源預處理。
    一次計算一勞永逸,避免在heightForRowAtIndexPath和cellForRowAtIndexPath方法裏每次滾到這一行都去計算。

  • 圖片預處理
    顯示之前提前處理。比如縮放:
    如果你是在畫cell的時候直接設置imageview的 contentMode 屬性讓 imageview自己縮放,縮放需要對圖片做transform ,要對圖片乘以一個變換矩陣,計算量很大,很耗性能,更何況你每次滾到那行都要縮放。
    所以可以在剛拿到圖片就先縮放生成新圖,當然最好是服務端能返回相對應大小的圖片,這樣就不用縮放了。而如果要看大圖,最好是點擊查看大圖的時候再拿大圖去渲染。設計時不宜把大圖直接顯示在列表裏。

  • 圖片異步加載
    圖片下載放到後臺,異步加載。
    不過如果每個循環對象都異步加載,很多個線程也會影響主線程的性能,所以好的方法就是按需加載,而不是全部在後臺下載

  • 圖片按需加載
    沒有在滑動且加速度爲0(即已經停止下來時)纔去加載圖片(不然你滑那麼快我每個圖都去請求網絡下載然後顯示,每個下載又是一個子線程,我線程各種切換,然後你刷地又滑回來。。。),且只去畫當前可視區域的圖片,或者再加上當前可視前後指定的幾行。
    下載過的不再下載,正在下載的不去下載。
    (按需加載可參考上篇文章,蘋果官方例子lazyloadImage的解析)

  • 圖片資源儘量用png,因爲iOS本身對png進行了很多優化

  • 關於reloaddata
    heightForRowAtIndexPath 在reload data時會執行所有cell高度全部刷新,而不是當前屏幕顯示的cell數量。儘量不用reload data而用insertrow deleterow

  • 關於重複代碼
    heightForRowAtIndexPath和cellForRowAtIndexPath中儘量不要有重複代碼

  • 儘量少用xib storyboard創建cell,他們需要系統自動轉碼,也不能自定義cell繪製

其次,關於cell上圖片圓角

  • cornerRadius方式
cell.myImageView.layer.cornerRadius = 8.0;
  cell.myImageView.layer.masksToBounds = YES;//或者cell.myImageView.clipsToBounds = YES

這種方式會觸發離屏渲染。iOS9之後png圖片不會了。

  • 設置masklayer
CAShapLayer *layer = [CAShapLayer layer];
UIBezierPath *bzpath = [UIBezierPath bezierPathWithOvalInRect:imageview.bounds] ;
layer.path = bzpath.CGPath;
cell.myImageView.layer.mask = layer

一次mask發生兩次離屏渲染和一次主屏渲染,比上面的方法效率還低。(主要是上下文切換耗性能)
但是可不侷限於圓角,由 mask 控制邊角顯示爲什麼形狀。

  • 光柵化,也叫位圖化
cell.myImageView.layer.cornerRadius = 8.0;
 cell.myImageView.layer.masksToBounds = YES;
cell.myImageView.layer.shouldRasterize = YES;
cell.myImageView.layer.rasterizationScale = [UIScreen mainScreen].scale;//UIImageView不加這句會產生一點模糊

設置光柵化,可以使離屏渲染的結果緩存到內存中存爲位圖,使用的時候直接使用緩存,節省了一直離屏渲染損耗的性能。但是如果layer及sublayers常常改變的話,它就會一直不停的渲染及刪除緩存重新創建緩存,此情況下建議不要使用光柵化,比較損耗性能

  • 對圖片進行切角

imageview的drawRect或者setImage裏

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);

[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.bounds.size.height/2] addClip];
[imageFromServer drawInRect:self.bounds];

circleImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[super setImage:circleImage]

切角的操作是在CPU內完成的,而後我們只需要取到處理完成的bitmap(可爲UIImage對象)交給GPU顯示於屏幕即可。
無離屏渲染,把 GPU的壓力轉給 CPU,適用於 CPU 壓力不大的情況,CPU和內存消耗增大。

  • mask圖和原圖合成
 UIImage *roundedImage = [self imageByComposingImage:image 
                                        withMaskImage:[UIImage imageNamed:@"mask.png"]];

  cell.myImageView.image = roundedImage;

- (UIImage *)imageByComposingImage:(UIImage *)image withMaskImage:(UIImage *)maskImage {
    CGImageRef maskImageRef = maskImage.CGImage; 
    CGImageRef maskRef = CGImageMaskCreate(CGImageGetWidth(maskImageRef),
                                         CGImageGetHeight(maskImageRef),
                                         CGImageGetBitsPerComponent(maskImageRef),
                                         CGImageGetBitsPerPixel(maskImageRef),
                                         CGImageGetBytesPerRow(maskImageRef),
                                         CGImageGetDataProvider(maskImageRef), NULL, false);

  CGImageRef newImageRef = CGImageCreateWithMask(image.CGImage, maskRef);
  CGImageRelease(maskRef);
  UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
  CGImageRelease(newImageRef);

  return newImage;
}

不侷限圓角,但效率比較低。

  • 直接覆蓋一張中間爲圓形透明的圖片
    直接[imageview addsubview:中間透明的圖片]
    然後再[imageview setImage:原圖]

這種方法就是多加了一張透明的圖片,GPU計算多層的混合渲染blending也是會消耗一點性能的

最後,關於Instrument來調試tableView性能

  • Core Animation
    debug options選擇:
    Color Hits Green and Misses Red:
    光柵化對應的渲染結果會被緩存。如果圖層是綠色,就表示這些緩存被複用;如果是紅色就表示緩存會被重複創建,這就表示該處存在性能問題了。

    Color Offscreen-Rendered Yellow開啓後會把那些需要離屏渲染的圖層高亮成黃色,這就意味着黃色圖層可能存在性能問題。

    Color misaligned images:
    洋紅色代表像素沒對齊???
    黃色代表圖片有縮放
    解決方法是設置各view 的 frame 時用整數不用小數??
    和最好剛拿到圖時就縮放生成新圖而不是顯示時再縮放

  • GPU Driver
    Renderer Utilization
    如果這個值超過了~50%,就意味着你的動畫可能對幀率有所限制,很可能因爲離屏渲染或者是重繪導致的過度混合

    Tiler Utilization
    如果這個值超過了~50%,就意味着你的動畫可能限制於幾何結構方面,也就是在屏幕上有太多的圖層佔用了。

    幀率Core animation frames per seconds 越接近60滑動越順暢。

  • time profiler
    Call Tree那邊設置全選,去查看哪句代碼走的時間比較長

參考:http://tutuge.me/2015/02/19/提升UITableView性能-複雜頁面的優化/
http://blog.csdn.net/hanmingsa/article/details/51648199

發佈了56 篇原創文章 · 獲贊 28 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章