iOS開發UITableView性能優化總結


UITableView是iOS開發中最常用的控件,UITableView性能優化也是老生常談了,大致總結如下,以供參考

1.    把賦值和計算布局以及數據綁定分離

    UITableView最核心的思想就是UlITableViewCell的重用機制。簡單的理解就是: UlTableView只 會創建一屏幕(或一屏幕多一點)的UITableViewCell,其他都是從中取出來重用的。每當Cell滑出屏幕時,就會放入到一個集合(或數組)中(這里就相當於一個重用池),當要顯示某一位置的Cell時,會先去集合(或數組)中取,如果有,就直接拿來顯示;如果沒有,才會創建。這樣做的好處可想而知,極大的減少了內存的開銷。

      思路是把賦值和計算布局分離。這樣讓tableView:cellForRowAtIndexPath:方法只 負責賦值,tableView:heightForRowAtIndexPath:方法只負責計算高度。注意:兩個方法盡可能的各司其職,不要重疊代碼!兩者都需要盡可能的簡單易算。Run一下,會發現UITableView滾動流暢 了很基於.上面的實現思路,我們可以在獲得數據后,直接先根據數據源計算出對應的布局,並緩存到數據源中,這樣在tableView:heightForRowAtIndexPath:方 法中就直接返回高度,而不需要每次都計算了。再一個就是我們經常在注意cellForRowAtIndexPath:中為每一.個cel綁定數據,實際上在調用.cellForRowAtIndexPath:的時候cell還沒有 被顯示出來。

(1)willDisplayCell

為了提高效率我們應該把數據綁定的操作放在cell顯示出來后再執行,可以在tableView: willDisplayCell: forRowAtIndexPath: (以后簡稱willDisplayCell)方法中綁定數據。注意illDisplayCell在cell在tableview展示之前就會調用,此時cell實例已經生成,所以不能更改cell的結構,只能是改動cell 上的UI的一些屬性(例如label的內容等) 。

(2)定義高度
1>新建一. 個繼承自UITableViewCel的類
2>重寫initWithStyle:reuseldentifier:方法
3>添加所有需要顯示的子控件(不需要設置子控件的數據和frame,子控件要添加到contentView中)
4>進行子控件一次性的屬性設 置(有些屬性只需要設置一次, 比如字體\固定的圖片)
5>提供2個模型
數據模型:存放文字數據\圖片數據.
frame模型:存放數據模型\所有子控件的frame\cell的高度
6>cell擁有一個frame模型(不要 直接擁有數據模型)
7>重寫frame模型屬性的setter方法:在這個方法中設置子控件的顯示數據和frame
(3)自定義高度原理
A手動計算
1>由於heightForRow比kcellForRow方法先調用,創建frame模型包含微博模型,重寫微博模型賦
值set方法,提前計算cell子控件的frame並保存,heightForRow方 法中取出frame模型中保存的高
度,實現自定義高度cll
2>設置最大尺寸、文本屬性,根據文本內容計算正文內容展示尺寸
3> cellForRow中創建自定義cell包含frame屬性,重寫frame屬性set方法創建cell子控件並賦值
frame模型保存的子控件尺寸
B.自動計算
1>首先設置行高使用autolayout自動計算並預估高度
2>在stroboard中對cel內容進行自動布局,注意設置圖片距離底部約束,cellForRow中 創建
storyboard中對應標記的自定義cell
3>由於正文內容的不確定性,設置label多行,拖線圖片高度約束,根據圖片有無,設置代碼設
置高度約束

2 自定義Cell的繪制

       上面的改進方法並不是最佳方案,但基本能滿足簡單的界面!記得開頭我的任務嗎?像朋友圈那樣的圖文混排,這種方案還是扛不住的!我們需要進入更深層次的探究:自定義Cell的繪制。我們在Cell.上添加系統控件的時候,實質上系統都需要調用底層的接口進行繪制,當我們大量添加控件時,對資源的開銷也會很大,所以我們可以索性直接繪制,提高效率。首先需要給自定義的Cell添加draw方法,(當 然也可以重寫drawRect)然后在方法體中實現:

(1) TableView渲染

為了保證TableView的流暢,當快速滑動的時候,cell必須被快速的渲 染出來。所以ell渲染的速度必須快。如何提高cel的渲染速度呢?當有圖像時,預渲染圖像,在bitmap context先將其畫一 -遍, 導出成Ullmage對象, 然后再繪制到屏幕,這會大大提高渲染速度。具體內容可以自行查找“利用預渲染加速顯示iOS圖像”相關資料(https://blog.csdn.net/qiaoxinde/article/details/50766844)。渲染最好時的操作之一就是混 合(blending)了,所以我們不要使用透明背景,將Cel的opaque 值設為Yes, 背景色不要使用clearColor, 盡量不要使用陰影漸變等由於混合操作是使用GPU來執行,我們可以用CPU來渲染,這樣混合操作就不再執行。可以在UIView的drawRect方法中自定義繪制。

(2)減少視圖的數目
我們在cell.上添加系統控件的時候,實際上系統都會調用底層的接口進行繪制,大量添加控件

時,會消耗很大的資源並且也會影響渲染的性能。當使用默認的UITableViewCell並且在它的ContentView.上面添加控件時會相當消耗性能。所以目前最佳的方法還是繼承UITableViewCell,並重寫drawRect方法。

(3)減少多余的繪制操作

在實現drawRect方法的時候,它的參數rect就是我們需要繪制的區域,在rect范圍之 外的區域我們不需要進行繪制,否則會消耗相當大的資源。

(4)不要給cell動態添加subView

在初始化cell的時候就將所有需要展示的添加完畢,然后根據需要來設置hide屬性顯示和隱藏。

(5)異步化UI,不要阻塞主線程

我們時常會看到這樣一個現象,就是加載時整個頁面卡住不動,怎么點都沒用,仿佛死機了一般。原因是主線程被阻塞了。所以對於網路數據的請求或者圖片的加載,我們可以開啟多線程,將耗時操作放到子線程中進行,異步化操作。這個或許每個iOS開發者都知道的知識,不必多講

3 滑動時按需加載對應的內容

如果目標行與當前行相差超過指定行數,只加載滑動結束時屏幕所見的cell。

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
                
        if loadarr.count > 0 && !loadarr.contains(indexPath) {
//不需要加載的
            cell.firstLabel.backgroundColor = .red
        }else{
//需要加載的
            cell.firstLabel.backgroundColor = .green
        }
    }
   
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        
        //目標行
        guard let indexPath = tableView!.indexPathForRow(at: CGPoint(x: 0, y: targetContentOffset.pointee.y)) else{
               return
        }
           
        //當前屏幕中顯示的第一條
        guard  let currentIP = tableView!.indexPathsForVisibleRows?.first else{
               return
        }
       //當滑動大於一定行數時,只加載最后目標屏幕的cell,其余的中間的要划過的cellr停止加載
        if abs(indexPath.row - currentIP.row) > 8 {
               
            guard let arr = tableView!.indexPathsForRows(in: CGRect(x: 0, y: targetContentOffset.pointee.y, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
            else{
                return
            }
               
           loadarr.append(contentsOf: arr)
               
        }
           
           
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let scrollstop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating
        if scrollstop {
//全局變量loadarr
            loadarr.removeAll()
        }
    }

 

 
4 離屏渲染問題

1.離屏渲染的問題的造成,下面的情況或操作會弓|發離屏渲染:

●為圖層設置遮罩(layer.mask)

●將圖層的layer.masks' ToBounds / view.clips ToBounds屬性設置為true

●將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小於1.0

●為圖層設置陰影(layer.shadow*) 。

●為圖層設置layer. shouldRasterize=true

●具有layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing的圖層

●文本(任何種類,包括UIL abel, CATextl _ayer, Core Text等)。

●使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現

2離屏渲染優化方案

官方對離屏渲染產生性能問題也進行了優化: 

ios 9.0之前UlimageView跟UlButton設置圓角都會觸發離屏渲染。

iOs 9.0之后UIButton設置圓角會觸發離屏渲染,而UllmageView里png圖片 設置圓角不會觸發離屏渲染了,如果設置其他陰影效果之類的還是會觸發離屏渲染的。

(1)圓角優化

在APP開發中,圓角圖片還是經常出現的。如果一個界面中只有少量圓角圖片或許對性能沒有非常大的影響,但是當圓角圖片比較多的時候就會APP性能產生明顯的影響。我們設置圓角一般通過如下方式:

imageView.layer.cornerRadius=CGFloat(10);

imageView.layer.masks ToBounds=YES;

這樣處理的渲染機制是GPU在當前屏幕緩沖區外新開辟一個 渲染緩沖區進行工作,也就是離屏渲染,這會給我們帶來額外的性能損耗,如果這樣的圓角操作達到一-定數量, 會觸發 緩沖區的頻繁合並和上下文的的頻繁切換,性能的代價會宏觀地表現在用戶體驗上一一掉幀。

優化方案:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

extension UIView {
    
    /// BezierPath 圓角設置
    func roundCorners(_ corners: UIRectCorner = .allCorners, radius: CGFloat) {
        let maskPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius))
        
        let shape = CAShapeLayer()
        shape.path = maskPath.cgPath
        layer.mask = shape
    }
}

(2) shadow優化
對於shadow,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設置shadowPath來優
化性能,能大幅提高性能。示例如下:

func setShadowLayer(_ views: [UIView], radius: CGFloat) {
    for view in views {
        let shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: radius)
        view.layer.masksToBounds = false
        view.layer.shadowColor = UIColor.black.cgColor
        view.layer.shadowOffset = CGSize(width: 3.0,height: 3.0)
        view.layer.shadowOpacity = 0.3
        view.layer.shadowPath = shadowPath.cgPath
    }
}

我們還可以通過設置shouldRasterize屬性值為YES來強制開啟離屏渲染。其實就是光柵化(Rasterization)。既然離屏渲染這么不好,為什么我們還要強制開啟呢?當一個圖像混合了多個圖層,每次移動時,每一幀都要重新合成這些圖層,十-分消耗性能。當我們開啟光柵化后,會在首次產生一個位圖緩存,當再次使用時候就會復用這個緩存。但是如果圖層發生改變的時候就會重新產生位圖緩存。所以這個功能一般不 能用於UITableViewCell中,cell的復 用反而降低了性能。最好用於圖層較多的靜態內容的圖形。而且產生的位圖緩存的大小是有限制的,一般是2.5個屏幕尺寸。在100ms之內不使用這個緩存,緩存也會被刪除。所以我們要根據使用場景而定。

 

(3)其他的一些優化建議

●當我們需要圓角效果時,可以使用- -張中間透明圖片蒙上去

●使用ShadowPath指定layer陰影效果路徑

●使用異步進行layer渲染(Facebook開 源的異步繪制框架AsyncDisplayKit)

●設置layer的opaque值為YES,減少復雜圖層合成

●盡量使用不包含透明(alpha) 通道的圖片資源

●盡量設置layer的大小值為整形值

●直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案

●很多情況下用戶.上傳圖片進行顯示,可以讓服務端處理圓角

●使用代碼手動生成圓角Image設置到要顯示的View.上,利用UIBezierPath (CoreGraphics框架)畫出來圓角圖片

(4) Core Animation工具檢測離屏渲染

對於離屏渲染的檢測,蘋果為我們提供了一個測試工具Core Animation。可以在Xcode- >Open Develeper Tools->Instruments中找到,如下圖:

 
page63image48021920
 Core Animation工具用來監測Core Animation性能,提供可見的FPS值,並且提供幾個選項來測量渲染性能。如下圖: 

 

下面我們來說明每個選項的功能:

Color Blended Layers:這個選項如果勾選,你能看到哪個layer是透明的,GPU正在做混合計算。顯示紅色的就是透明的,綠色就是不透明的。Color Hits Green and Misses Red:如果勾選這個選項,且當我們代碼中有設置shouldRasterize為YES,那么紅色代表沒有復用離屏渲染的緩存,綠色則表示復用了緩存。我們當然希望能夠復用。Color Copied Images:按照官方的說法,當圖片的顏色格式GPU不支持的時候,Core Animation會拷貝一份數據讓CPU進行轉化。例如從網絡.上下載了TIFF格式的圖片,則需要CPU進行轉化,這個區域會顯示成藍色。還有一種情況會觸發Core Animation的copy方法,就是字體不對齊的時候。如下圖:

 

色,如果勾選這個選項則移除10ms的延遲。對某些情況需要這樣,但是有可能影響正常幀數的測試。Color Misaligned Ilmages:勾選此項,如果圖片需要縮放則標記為黃色,如果沒有像素對齊則標記為紫色。像素對齊我們已經在上面有所介紹。Color Offscreen-Rendered Yellow:用來檢測離屏渲染的,如果顯示黃色,表示有離屏渲染。當然還要結合Color Hits Green and Misses Red來看,是否復用了緩存。Color OpenGL Fast Path Blue:這個選項對那些使用OpenGL的圖層才有用,像是GL KView或者CAEAGLL ayer,如果不顯示藍色則表示使用了CPU渲染,繪制在了屏幕外,顯示藍色表示正常。Flash Updated Regions:當對圖層重繪的時候回顯示黃色,如果頻繁發生則會影響性能。可以用增加緩存來增強性能。

性能優化是一個持續的過程,未完待續。。。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM