作者:方秋枋(微博)
學習與延伸
這篇文章主要是學習完Advanced Graphics and Animations for iOS Apps這個session後的總結和相應細節的延伸和細化。主要內容為圖形性能與測試工具這兩個章節。
目錄:
Core Animation Pipeline
UIBlurEffectView
圖形性能
測試工具
總結
Core Animation Pipeline
第一部分主要講解了Core Animation的工作流程和渲染過程。
CoreAnimation的渲染流程可以用下圖來概括:
在GPU的渲染過程中,我們能看到頂點著色器與像素著色器參與到圖像的處理。
在objc.io中有一篇文章進一步地闡明了頂點著色器與像素著色器 (GPU 加速下的圖像處理)
UIBlurEffectView
第二部分主要講解了iOS8新引入的UIBlurEffectView,結合第一部分來闡述UIBlurEffectView是如何工作的,以及它們的性能。
事實上,個人覺得有一點很有趣。就是UIBlurEffectView為了優化圖像處理效率,並不是用普通的模糊算法。雖然Session中沒提到模糊算法,我在這裡簡單地介紹一下簡單的模糊算法。
最簡單模糊的過程即是用中心像素與其周圍像素的顏色算術平均值來代表模糊後的顏色值。我們可以在下面兩幅圖看到中心像素值的變化。
那麼蘋果是怎麼做的呢?最有趣的一點是它並不是對原始圖像直接模糊,而是先將圖像縮放之後再進行模糊。這樣的優點就是模糊算法需要處理的像素點就減少了,處理的效率會更快。
看到這裡的時候我是笑了的,哈哈,覺得很機智。適當的把思維跳出來,"偷點懶",有時真的會取得很不錯的效果。學習,學習。
其次就是水平模糊與豎直模糊後再合成,添加顏色。
最後需要關注和有趣的一點是:蘋果為我們提供了三個UIBlurEffect styles,
分別為Extra light, Light, Dark.但是三者的耗費的資源各為不同。
Extra light耗費最多資源, Light其次, Dark最多。
我在自己的個人項目裡也有用到UIBlurEffectView來美化界面,優化用戶體驗。兩個項目都已經上架,並完整開源。歡迎去看看。
TouchColor
主要使用在主菜單界面。
QR Catcher
用在了實時蒙版,即類似微信二維碼掃描框外的黑色半透明背景,在這裡則是實時模糊,更美觀。
圖形性能
關於圖形性能在之前關注的不夠多,主要是用前人總結好的比較教條式的優化方式。這次借這個Session的學習,繼續往外擴展閱讀學習,好好梳理和學習遺漏點,底層細節,原理與性能優化的工具
1. 關於CALayer的shouldRasterize(光柵化)
開啟shouldRasterize後,CALayer會被光柵化為bitmap,layer的陰影等效果也會被保存到bitmap中。
當我們開啟光柵化後,需要注意三點問題。
如果我們更新已光柵化的layer,會造成大量的offscreen渲染。
因此CALayer的光柵化選項的開啟與否需要我們仔細衡量使用場景。只能用在圖像內容不變的前提下的:
① 用於避免靜態內容的復雜特效的重繪,例如前面講到的UIBlurEffect
② 用於避免多個View嵌套的復雜View的重繪。
而對於經常變動的內容,這個時候不要開啟,否則會造成性能的浪費。
例如我們日程經常打交道的TableViewCell,因為TableViewCell的重繪是很頻繁的(因為Cell的復用),如果Cell的內容不斷變化,則Cell需要不斷重繪,如果此時設置了cell.layer可光柵化。則會造成大量的offscreen渲染,降低圖形性能。
當然,合理利用的話,是能夠得到不少性能的提高的,因為使用shouldRasterize後layer會緩存為Bitmap位圖,對一些添加了shawdow等效果的耗費資源較多的靜態內容進行緩存,能夠得到性能的提升。
不要過度使用,系統限制了緩存的大小為2.5X Screen Size.
如果過度使用,超出緩存之後,同樣會造成大量的offscreen渲染。
被光柵化的圖片如果超過100ms沒有被使用,則會被移除
因此我們應該只對連續不斷使用的圖片進行緩存。對於不常使用的圖片緩存是沒有意義,且耗費資源的。
2. 關於offscreen rendering
注意到上面提到的offscreen rendering。我們需要注意shouldRasterize的地方就是會造成offscreen rendering的地方,那麼為什麼需要避免呢?
WWDC 2011 Understanding UIKit Rendering指出一般導致圖形性能的問題大部分都出在了offscreen rendering,因此如果我們發現列表滾動不流暢,動畫卡頓等問題,就可以想想和找出我們哪部分代碼導致了大量的offscreen 渲染。
首先,什麼是offscreen rendering?
offscreen rendring指的是在圖像在繪制到當前屏幕前,需要先進行一次渲染,之後才繪制到當前屏幕。
那麼為什麼offscreen渲染會耗費大量資源呢?
原因是顯卡需要另外alloc一塊內存來進行渲染,渲染完畢後在繪制到當前屏幕,而且對於顯卡來說,onscreen到offscreen的上下文環境切換是非常昂貴的(涉及到OpenGL的pipelines和barrier等),
備注:
這裡提到的offscreen rendering主要講的是通過GPU執行的offscreen,事實上還有的offscreen rendering是通過CPU來執行的(例如使用Core Graphics, drawRect)。其它類似cornerRadios, masks, shadows等觸發的offscreen是基於GPU的。
許多人有誤區,認為offscreen rendering就是software rendering,只是純粹地靠CPU運算。實際上並不是的,offscreen rendering是個比較復雜,涉及許多方面的內容。
我們在開發應用,提高性能通常要注意的是避免offscreen rendering。不需要糾結和拘泥於它的定義。
有興趣可以繼續閱讀Andy Matuschak, 前UIKit team成員關於offscreen rendering的評論。
總之,我們通常需要避免大量的offscreen rendering.
會造成 offscreen rendering的原因有:
Any layer with a mask (layer.mask)
Any layer with layer.masksToBounds being true
Any layer with layer.allowsGroupOpacity set to YES and layer.opacity is less than 1.0
Any layer with a drop shadow (layer.shadow*).
Any layer with layer.shouldRasterize being true
Any layer with layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing
因此,對於一些需要優化圖像性能的場景,我們可以檢查我們是否觸發了offscreen rendering。 並用更高效的實現手段來替換。
例如:
① 陰影繪制:使用ShadowPath來替代shadowOffset等屬性的設置。
一個如圖的簡單tableView:
兩種不同方式來繪制陰影:
不使用shadowPath
CALayer *imageViewLayer = cell.imageView.layer; imageViewLayer.shadowColor = [UIColor blackColor].CGColor; imageViewLayer.shadowOpacity = 1.0; imageViewLayer.shadowRadius = 2.0; imageViewLayer.shadowOffset = CGSizeMake(1.0, 1.0);
使用shadowPath
imageViewLayer.shadowPath = CGPathCreateWithRect(imageRect, NULL);
我們可以在下圖看到兩種方式巨大的性能差別。
個人推測的shadowPath高效的原因是使用shadowPath避免了offscreen渲染,因為僅需要直接繪制路徑即可,不需要提前讀取圖像去渲染。
②裁剪圖片為圓:
如圖為例
使用CornerRadius:
CALayer *imageViewLayer = cell.imageView.layer; imageViewLayer.cornerRadius = imageHeight / 2.0; imageViewLayer.masksToBounds = YES;
利用一張中間為透明圓形的圖片來進行遮蓋,雖然會引起blending,但性能仍然高於offerScreen。
根據蘋果測試,第二種方式比第一種方式更高效:
以上舉了兩個例子闡明了在避免大量的offerScreen渲染後,性能能夠得到非常直觀有效的提高。
③關於blending
前面提到了用透明圓形的圖片來進行遮蓋,會引起blending。blending也會耗費性能。
:) 笑。如果閱讀這篇文章的讀者看到這裡,是不是覺得已經無眼看下去了。哈哈,我自己學習總結到這裡也是感受到了長路慢慢,但是我們仍然還是要不斷上下求索的。 :)
好了 接下來讓我們來認識一下Blending.
什麼是Blending?
在iOS的圖形處理中,blending主要指的是混合像素顏色的計算。最直觀的例子就是,我們把兩個圖層疊加在一起,如果第一個圖層的透明的,則最終像素的顏色計算需要將第二個圖層也考慮進來。這一過程即為Blending。
會導致blending的原因:
layer(UIView)的Alpha < 1
UIImgaeView的image含有Alpha channel(即使UIImageView的alpha是1,但只要image含透明通道,則仍會導致Blending)
為什麼Blending會導致性能的損失?
原因是很直觀的,如果一個圖層是不透明的,則系統直接顯示該圖層的顏色即可。而如果圖層是透明的,則會引入更多的計算,因為需要把下面的圖層也包括進來,進行混合後顏色的計算。
在了解完Blending之後,我們就知道為什麼很多優化准則都需要我們盡量使用不透明圖層了。接下來就是在開發中留意和進行優化了。
測試工具
在出現圖像性能問題,滑動,動畫不夠流暢之後,我們首先要做的就是定位出問題的所在。而這個過程並不是只靠經驗和窮舉法探索,我們應該用有脈絡,有順序的科學的手段進行探索。
首先,我們要有一個定位問題的模式。我們可以按照這樣的順序來逐步定位,發現問題。
定位幀率,為了給用戶流暢的感受,我們需要保持幀率在60幀左右。當遇到問題後,我們首先檢查一下幀率是否保持在60幀。
定位瓶頸,究竟是CPU還是GPU。我們希望占用率越少越好,一是為了流暢性,二也節省了電力。
檢查有沒有做無必要的CPU渲染,例如有些地方我們重寫了drawRect,而其實是我們不需要也不應該的。我們希望GPU負責更多的工作。
檢查有沒有過多的offscreen渲染,這會耗費GPU的資源,像前面已經分析的到的。offscreen 渲染會導致GPU需要不斷地onScreen和offscreen進行上下文切換。我們希望有更少的offscreen渲染。
檢查我們有無過多的Blending,GPU渲染一個不透明的圖層更省資源。
檢查圖片的格式是否為常用格式,大小是否正常。如果一個圖片格式不被GPU所支持,則只能通過CPU來渲染。一般我們在iOS開發中都應該用PNG格式,之前閱讀過的一些資料也有指出蘋果特意為PNG格式做了渲染和壓縮算法上的優化。
檢查是否有耗費資源多的View或效果。我們需要合理有節制的使用。像之前提到的UIBlurEffect就是一個例子。
最後,我們需要檢查在我們View層級中是否有不正確的地方。例如有時我們不斷的添加或移除View,有時就會在不經意間導致bug的發生。像我之前就遇到過不斷添加View的一個低級錯誤。我們希望在View層級中只包含了我們想要的東西。
OK,當我們有了一套模式之後,就可以使用蘋果為我們提供的優秀測試工具來進行測試了。
對於圖形性能問題的地位。一般我們有下列測試工具:
Instruments裡的:
Core Animation instrument
OpenGL ES Driver instrument
模擬器中的:
Color debug options View debugging
還有Xcode的:
View debugging
然後我們來根據上面定位問題的模式來選擇相應測試工具:
定位幀率
定位瓶頸
檢查有無必要的CPU渲染
以上三點我們可以使用CoreAnimation instrument來測試。
CoreAnimation instrument包含了兩個模塊。第一幅圖展示了檢測幀率。第二幅圖展示了檢測CPU調用。我們能夠通過它們來進行上述三個問題的檢測。注意到第二幅圖左下角,那是CPU 的call stack.我們就是在這裡檢測我們有沒有做無必要的drawRect,有沒有在主線程做太多事務導致阻塞了UI更新。
關於GPU的瓶頸問題,我們可以通過OpenGL ES Driver instrument來獲得更詳細的信息。例如GPU的占用率。可以看到下圖左下角有顯示Device utilization。
4. 檢查有無過多offscreen渲染
5. 檢查有無過多Blending
6. 檢查有無不正確圖片格式,圖片是否被放縮,像素是否對齊。
7. 檢查有無使用復雜的圖形效果。
以上這四點我們同樣使用CoreAnimation instrument來測試。
我們可以看到上圖右下角的Debug options有多個選項。我們通過勾選這些選項來觸發Color Debug。下面逐個對這些選項進行分析。
Color Blended layers
如圖,勾選這個選項後,blended layer 就會被顯示為紅色,而不透明的layer則是綠色。我們希望越少紅色區域越好。
Color Hits Green and Misses Red
這個選項主要是檢測我們有無濫用或正確使用layer的shouldRasterize屬性.成功被緩存的layer會標注為綠色,沒有成功緩存的會標注為紅色。
在測試的過程中,第一次加載時,開啟光柵化的layer會顯示為紅色,這是很正常的,因為還沒有緩存成功。但是如果在接下來的測試,例如我們來回滾動TableView時,我們仍然發現有許多紅色區域,那就需要謹慎對待了。因為像我們前面討論過的,這會引起offscreen rendering。
檢查一下是否有濫用該屬性,因為系統規定的緩存大小是屏幕大小的2.5倍,如果使用過度,超出了緩存大小,會引起offscreen rendering。檢測layer是否內容不斷更新,內容的更新會導致緩存失效和大量的offscreen rendering.
Color copied images
這個選項主要檢查我們有無使用不正確圖片格式,若是GPU不支持的色彩格式的圖片則會標記為青色,則只能由CPU來進行處理。我們不希望在滾動視圖的時候,CPU實時來進行處理,因為有可能會阻塞主線程。
Color misaligned images
這個選項檢查了圖片是否被放縮,像素是否對齊。被放縮的圖片會被標記為黃色,像素不對齊則會標注為紫色。
Color offscreen-rendered yellow
這個選項將需要offscreen渲染的的layer標記為黃色。
以上圖為例子,NavigationBar和ToolBar被標記為黃色。因為它們需要模糊背後的內容,這需要offscreen渲染。但是這是我們需要的。而圖片也是被標記為黃色,那是因為陰影的緣故。我前面已經提到了這一點,如果此時我們用shadowPath來替代的話,就能夠避免offscreen渲染帶來的巨大開銷。
Color OpenGL fast path blue
這個選項勾選後,由OpenGL compositor進行繪制的圖層會標記為藍色。這是一個好的結果。
Flash updated regions
會標記屏幕上被快速更新的部分為黃色,我們希望只是更新的部分被標記完黃色。
好啦,終於完整介紹完這些調試選項了,我們總結一下。
我們需要重點注意的是
①Color Blended layers
②Color Hits Green and Misses Red
③Color offscreen-rendered yellow這三個選項。
因為這三個部分對性能的影響最大。
8. 檢查View層級是否正確。
我們可以在上圖清楚地看到View的層級關系。可以檢查View的層級是否正確。
小提示(應用運行後,在這裡打開):從左往右第七個圖
總結
關於圖形性能還有許多細節和底層可以深入,不過經過這一次總結與學習,基本把握了iOS圖形性能的優化細節和工具。希望也能夠對你有一點幫助。
在學習和探索的過程中,個人感受最深的是兩點。
1. 一類事情背後都會有一定的原理,弄清楚了原理就能更好地把握這一類事務。
在之前的iOS開發中,對圖形界面的優化主要處於用前人總結的教條來優化。而經過這次學習之後,明白這些教條背後的原理,像最影響性能的offscreen rendering和blending。更能有針對性的優化和分析。
2. 檢測問題不應該是盲目的,有一定的模式和工具會更清晰。
像對圖形性能的問題定位,我們不應該一上來就開始找問題,看代碼。而是應該逐步定位。而是像前面總結的一樣,定位幀率,摸清瓶頸,逐個問題擊破。再配合合適的工具進行測試和定位,一定能夠提升效率和准確度。
如有疑問,可聯系作者討論。