iOS進階之頁面性能優化

前言html

在軟件開發領域裏常常能聽到這樣一句話,「過早的優化是萬惡之源」,不要過早優化或者過分優化。我認爲在編碼過程當中時刻注意性能影響是有必要的,但凡事都有個度,不能爲了性能耽誤了開發進度。在時間緊急的狀況下咱們每每採用「quick and dirty」的方案來快速出成果,後面再迭代優化,即所謂的敏捷開發。與之相對應的是傳統軟件開發中的瀑布流開發流程。ios

卡頓產生的緣由緩存

128529-9a2f28de5bc7beae.jpg

image性能優化

在 iOS 系統中,圖像內容展現到屏幕的過程須要 CPU 和 GPU 共同參與。CPU 負責計算顯示內容,好比視圖的建立、佈局計算、圖片解碼、文本繪製等。隨後 CPU 會將計算好的內容提交到 GPU 去,由 GPU 進行變換、合成、渲染。以後 GPU 會把渲染結果提交到幀緩衝區去,等待下一次 VSync 信號到來時顯示到屏幕上。因爲垂直同步的機制,若是在一個 VSync 時間內,CPU 或者 GPU 沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留以前的內容不變。這就是界面卡頓的緣由。微信

所以,咱們須要平衡 CPU 和 GPU 的負荷避免一方超負荷運算。爲了作到這一點,咱們首先得了解 CPU 和 GPU 各自負責哪些內容。app

128529-98d2cdc063b500d8.jpg

imageasync

上面的圖展現了 iOS 系統下各個模塊所處的位置,下面咱們再具體看一下 CPU 和 GPU 對應了哪些操做。ide

CPU 消耗型任務函數

佈局計算工具

佈局計算是 iOS 中最爲常見的消耗 CPU 資源的地方,若是視圖層級關係比較複雜,計算出全部圖層的佈局信息就會消耗一部分時間。所以咱們應該儘可能提早計算好佈局信息,而後在合適的時機調整對應的屬性。還要避免沒必要要的更新,只在真正發生了佈局改變時再更新。

對象建立

對象建立過程伴隨着內存分配、屬性設置、甚至還有讀取文件等操做,比較消耗 CPU 資源。儘可能用輕量的對象代替重量的對象,能夠對性能有所優化。好比 CALayer 比 UIView 要輕量許多,若是視圖元素不須要響應觸摸事件,用 CALayer 會更加合適。

經過 Storyboard 建立視圖對象還會涉及到文件反序列化操做,其資源消耗會比直接經過代碼建立對象要大很是多,在性能敏感的界面裏,Storyboard 並非一個好的技術選擇。

對於列表類型的頁面,還能夠參考 UITableView 的複用機制。每次要初始化 View 對象時先根據 identifier 從緩存池裏取,能取到就複用這個 View 對象,取不到再真正執行初始化過程。滑動屏幕時,會將滑出屏幕外的 View 對象根據 identifier 放入緩存池,新進入屏幕可見範圍內的 View 又根據前面的規則來決定是否要真正初始化。

Autolayout

Autolayout 是蘋果在 iOS6 以後新引入的佈局技術,在大多數狀況下這一技術都能大大提高開發速度,特別是在須要處理多語言時。好比阿拉伯語下佈局是從右往左,經過 Autolayout 設置 leading 和 trailing 便可。

可是 Autolayout 對於複雜視圖來講經常會產生嚴重的性能問題,對於性能敏感的頁面建議仍是使用手動佈局的方式,並控制好刷新頻率,作到真正須要調整佈局時再從新佈局。

文本計算

若是一個界面中包含大量文本(好比微博、微信朋友圈等),文本的寬高計算會佔用很大一部分資源,而且不可避免。

一個比較常見的場景是在 UITableView 中,heightForRowAtIndexPath這個方法會被頻繁調用,即便不是耗時的計算在調用次數多了以後也會帶來性能損耗。這裏的優化就是儘可能避免每次都從新進行文本的行高計算,能夠在獲取到 Model 數據後就根據文本內容計算好佈局信息,而後將這份佈局信息做爲一個屬性保存到對應的 Model 中,這樣在 UITableView 的回調中就能夠直接使用 Model 中的屬性,減小了文本的計算。

文本渲染

128529-c7bb06f76ac2730a.jpg

屏幕上能看到的全部文本內容控件,包括 UIWebView,在底層都是經過 CoreText 排版、繪製爲 Bitmap 顯示的。常見的文本控件 (UILabel、UITextView 等),其排版和繪製都是在主線程進行的,當顯示大量文本時,CPU 的壓力會很是大。

這一部分的性能優化就須要咱們放棄使用系統提供的上層控件轉而直接使用 CoreText 進行排版控制。

Wherever possible, try to avoid making changes to the frame of a view that contains text, because it will cause the text to be redrawn. For example, if you need to display a static block of text in the corner of a layer that frequently changes size, put the text in a sublayer instead.

上面這段話引用自 iOS Core Animation: Advanced Techniques,翻譯過來的意思就是說包含文本的視圖在改變佈局時會觸發文本的從新渲染,對於靜態文本咱們應該儘可能減小它所在視圖的佈局修改。

圖像的繪製

圖像的繪製一般是指用那些以 CG 開頭的方法把圖像繪製到畫布中,而後從畫布建立圖片並顯示的過程。前面的模塊圖裏介紹了 CoreGraphic 是做用在 CPU 之上的,所以調用 CG 開頭的方法消耗的是 CPU 資源。咱們能夠將繪製過程放到後臺線程,而後在主線程裏將結果設置到 layer 的 contents 中。代碼以下:

- (void)display {

    dispatch_async(backgroundQueue, ^{

        CGContextRef ctx = CGBitmapContextCreate(...);

        // draw in context...

        CGImageRef img = CGBitmapContextCreateImage(ctx);

        CFRelease(ctx);

        dispatch_async(mainQueue, ^{

            layer.contents = img;

        });

    });

}

圖片的解碼

Once an image file has been loaded, it must then be decompressed. This decompression can be a computationally complex task and take considerable time. The decompressed image will also use substantially more memory than the original.

圖片被加載後須要解碼,圖片的解碼是一個複雜耗時的過程,而且須要佔用比原始圖片還多的內存資源。

爲了節省內存,iOS 系統會延遲解碼過程, 在圖片被設置到 layer 的 contents 屬性或者設置成 UIImageView 的 image 屬性後纔會執行解碼過程,可是這兩個操做都是在主線程進行,仍是會帶來性能問題。

若是想要提早解碼,可使用 ImageIO 或者提早將圖片繪製到 CGContext 中,這部分實踐能夠參考 iOS Core Animation: Advanced Techniques

這裏多提一點,經常使用的 UIImage 加載方法有 imageNamedimageWithContentsOfFile。其中 imageNamed 加載圖片後會立刻解碼,而且系統會將解碼後的圖片緩存起來,可是這個緩存策略是不公開的,咱們沒法知道圖片何時會被釋放。所以在一些性能敏感的頁面,咱們還能夠用 static 變量 hold 住 imageNamed 加載到的圖片避免被釋放掉,以空間換時間的方式來提升性能。

GPU消耗型任務

相對於 CPU 來講,GPU 能幹的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形),應用變換(transform)、混合並渲染,而後輸出到屏幕上。寬泛的說,大多數 CALayer 的屬性都是用 GPU 來繪製。

如下一些操做會下降 GPU 繪製的性能,

大量幾何結構

全部的 Bitmap,包括圖片、文本、柵格化的內容,最終都要由內存提交到顯存,綁定爲 GPU Texture。不管是提交到顯存的過程,仍是 GPU 調整和渲染 Texture 的過程,都要消耗很多 GPU 資源。當在較短期顯示大量圖片時(好比 TableView 存在很是多的圖片而且快速滑動時),CPU 佔用率很低,GPU 佔用很是高,界面仍然會掉幀。避免這種狀況的方法只能是儘可能減小在短期內大量圖片的顯示,儘量將多張圖片合成爲一張進行顯示。

另外當圖片過大,超過 GPU 的最大紋理尺寸時,圖片須要先由 CPU 進行預處理,這對 CPU 和 GPU 都會帶來額外的資源消耗。

視圖的混合

當多個視圖(或者說 CALayer)重疊在一塊兒顯示時,GPU 會首先把他們混合到一塊兒。若是視圖結構過於複雜,混合的過程也會消耗不少 GPU 資源。爲了減輕這種狀況的 GPU 消耗,應用應當儘可能減小視圖數量和層次,而且減小沒必要要的透明視圖。

離屏渲染

離屏渲染是指圖層在被顯示以前是在當前屏幕緩衝區之外開闢的一個緩衝區進行渲染操做。

離屏渲染須要屢次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束之後,將離屏緩衝區的渲染結果顯示到屏幕上又須要將上下文環境從離屏切換到當前屏幕,而上下文環境的切換是一項高開銷的動做。

會形成 offscreen rendering 的緣由有:

  • 陰影(UIView.layer.shadowOffset/shadowRadius/…)
  • 圓角(當 UIView.layer.cornerRadius 和 UIView.layer.maskToBounds 一塊兒使用時)
  • 圖層蒙板
  • 開啓光柵化(shouldRasterize = true)

使用陰影時同時設置 shadowPath 就能避免離屏渲染大大提高性能,後面會有一個 Demo 來演示;圓角觸發的離屏渲染能夠用 CoreGraphics 將圖片處理成圓角來避免。

CALayer 有一個 shouldRasterize 屬性,將這個屬性設置成 true 後就開啓了光柵化。開啓光柵化後會將圖層繪製到一個屏幕外的圖像,而後這個圖像將會被緩存起來並繪製到實際圖層的 contents 和子圖層,對於有不少的子圖層或者有複雜的效果應用,這樣作就會比重繪全部事務的全部幀來更加高效。可是光柵化原始圖像須要時間,並且會消耗額外的內存。

光柵化也會帶來必定的性能損耗,是否要開啓就要根據實際的使用場景了,圖層內容頻繁變化時不建議使用。最好仍是用 Instruments 比對開啓先後的 FPS 來看是否起到了優化效果。

注意:

shouldRasterize = true 時記得同時設置 rasterizationScale

Instruments 使用

128529-6532658361e28a6f.jpg

image

Instruments 是一系列工具集,咱們這裏只演示 Core Animation 的使用。在 Core Animation 選項右下方會看到以下選項,

128529-019d410d3271bd63.jpg

image

Color Blended Layers

這個選項選項基於渲染程度對屏幕中的混合區域進行綠到紅的高亮顯示,越紅表示性能越差,會對幀率等指標形成較大的影響。紅色一般是因爲多個半透明圖層疊加引發。

Color Hits Green and Misses Red

當 UIView.layer.shouldRasterize = YES 時,耗時的圖片繪製會被緩存,並當作一個簡單的扁平圖片來呈現。這時候,若是頁面的其餘區塊(好比 UITableViewCell 的複用)使用緩存直接命中,就顯示綠色,反之,若是不命中,這時就顯示紅色。紅色越多,性能越差。由於柵格化生成緩存的過程是有開銷的,若是緩存能被大量命中和有效使用,則整體上會下降開銷,反之則意味着要頻繁生成新的緩存,這會讓性能問題雪上加霜。

Color Copied Images

對於 GPU 不支持的色彩格式的圖片只能由 CPU 來處理,把這樣的圖片標爲藍色。藍色越多,性能越差。

Color Immediately

一般 Core Animation Instruments 以每毫秒 10 次的頻率更新圖層調試顏色。對某些效果來講,這顯然太慢了。這個選項就能夠用來設置每幀都更新(可能會影響到渲染性能,並且會致使幀率測量不許,因此不要一直都設置它)。

Color Misaligned Images

這個選項檢查了圖片是否被縮放,以及像素是否對齊。被放縮的圖片會被標記爲黃色,像素不對齊則會標註爲紫色。黃色、紫色越多,性能越差。

Color Offscreen-Rendered Yellow

這個選項會把那些離屏渲染的圖層顯示爲黃色。黃色越多,性能越差。這些顯示爲黃色的圖層極可能須要用 shadowPath 或者 shouldRasterize 來優化。

Color OpenGL Fast Path Blue

這個選項會把任何直接使用 OpenGL 繪製的圖層顯示爲藍色。藍色越多,性能越好。若是僅僅使用 UIKit 或者 Core Animation 的 API,那麼不會有任何效果。

Flash Updated Regions

這個選項會把重繪的內容顯示爲黃色。不應出現的黃色越多,性能越差。一般咱們但願只是更新的部分被標記完黃色。

演示

上述幾個選項中經常使用來檢測性能的是 Color Blended Layers、Offscreen-Rendered Yellow 和 Color Hits Green and Misses Red。下面我重點演示一下離屏渲染和光柵化的檢測,寫了一個簡單的 Demo 設置了陰影效果,代碼以下:

    view.layer.shadowOffset = CGSizeMake(1, 1);

    view.layer.shadowOpacity = 1.0;

    view.layer.shadowRadius = 2.0;

    view.layer.shadowColor = [UIColor blackColor].CGColor;

//    view.layer.shadowPath = CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL);

shadowPath 沒有設置時用 Instruments 檢測 FPS 基本在 20 如下(iPhone6設備),設置了 shadowPath 後基本維持在 55 左右,性能提高十分明顯。

下面來看一下光柵化的檢測,代碼以下,

    view.layer.shouldRasterize = YES;

    view.layer.rasterizationScale = [UIScreen mainScreen].scale;

勾選 Color Hits Green and Misses Red 選項後顯示以下:

128529-54393ba0d3a3ffef.gif

image

咱們能夠看到在靜止時緩存都生效了,在快速滑動時緩存基本不起做用,所以是否要開啓光柵化仍是得根據具體場景,用 Instruments 檢測開啓先後的性能來決定。

總結

本文主要總結了性能調優的一些理論知識,後面還介紹了 Instruments 中 Core Animation 的一些性能檢測指標用法。性能優化最重要的是要使用工具來檢測而不是猜想,先查看是否有離屏渲染等問題,再用 Time Profiler 分析一下耗時的函數調用。修改後再用工具分析是否有改善,一步一步執行,當心仔細。

建議你們也實際動手分析一下本身的應用,加深一下印象,enjoy~

參考資料

https://www.jianshu.com/p/1b5cbf155b31

相關文章
相關標籤/搜索