原標題為:《Advanced Graphics and Animation For iOS Apps》。下載的文件名有點不同。本筆記內容為視頻學習和實踐探索。另外 objc 的這篇文章《繪制像素到屏幕上》和本視頻的內容有很大程度的交集,推薦一下。
視頻內容
- Core Animation pipeline
- Rendering concepts
- UIBlurEffect
- UIVibrancyEffect
- Profiling tools
- Case studies
Core Animation Pipeline
這個章節的時長為06:35,但信息量非常大。每個小節拉出來都能另寫一章。
讓屏幕頁面流暢應該保證頁面刷新率為 60 幀/秒,1 幀的時間大概就是 16.67 ms了。Core Animation 這個框架的名字很具有誤導性,讓大家以為這個框架只是用來實現動畫的,實際上 Core Animation 框架做了很多基礎工作:組合屏幕上的內容,追蹤視圖結構和內容的變化。流程圖中 Commit Transaction 前面的紅框代表觸發視圖內容變化的事件,比如點擊按鈕,之后Core Animation 框架會捕獲到屏幕內容的變化并提交給 Render Server(渲染服務器),Render Server 里另外一個版本的 Core Animation 框架負責解碼并繪制內容。
值得注意的是:在 Prepare 這個階段做的事是圖像解碼以及圖像轉換。無論是網上下載的圖像還是從磁盤讀取圖像文件,得到的圖像一般是不能直接用于顯示的,需要解碼為位圖(bitmap)。如果你的視圖中使用了 JPEGs 或是 PNGs的圖像,將在這個階段進行解碼;如果你使用了 GPU 不支持的圖像格式(就是 JPEG 和 PNG 之外的格式),那么圖像就需要轉換格式。Path 團隊的圖像緩存開源庫FastImageCache就利用這個特性來加速圖像的顯示。
Rendering concepts
這一節介紹了一些基本的渲染知識:屏幕被分割成 NxN 像素的小塊來渲染,每個小塊的大小與 SoC(System on Chip) 的cache相關。具體的操作過程如下:對于一個 app icon,被當做一個 CALayer 來渲染,而 CALayer 在 Core Animation 中被劃分為兩個三角形,每個三角形可以被繼續分割成多個三角形,對每一個三角形單獨渲染。
后面的,我看完之后基本就忘了,所以想了解到底怎么渲染的可以看看這一段。大致過程是渲染然后合成,在開頭的文章中有對這塊的敘述。
UIVisualEffectView
在 iOS 8 中蘋果放出了新的 UIView
子類 UIVisualEffectView
。蘋果在 iOS 7 中廣泛使用了虛化效果,卻沒有給出接口,民間大多使用 GPUImage 這個庫來實現虛化效果,終于在 iOS 8 里給出了官方支持。UIVisualEffectView
支持 UIBlurEffect 和 UIVibrancyEffect 兩種效果,前者是虛化內容,后者在前者的基礎上再合成一個透明視圖。
UIBlurEffect(虛化效果)的實現正是基于上一節提到的渲染+合成。其手法是這樣:抓取用作背景的內容然后進行縮放以降低計算量,然后分別進行水平虛化和垂直虛化(翻譯得可能不對,就是橫著來一下,然后豎著來一下,而不是直接對整個內容進行虛化,降低計算量),最后將縮小的內容再放大到原來的尺寸進行著色。
效果有三種樣式:Extra light, Light, Dark。這三種樣式對性能的要求依次降低。另外 iPad 2以及 iPad 3不支持虛化(who care?)。而 UIVibrancyEffect 效果是在 UIBlurEffect 的基礎上再合成一個視圖。UIVibrancyEffect 效果對性能要求極高,蘋果工程師建議避免對全屏使用該效果,并給出了優化性能的建議:Rasterization 和 Group Opacity。
1.Rasterization
設置 CALayer 的 shouldRasterize 屬性為 YES 能夠為程序觸發離屏渲染(Offscreen Rendering),更新內容時能夠額外渲染不在屏幕范圍上的內容用作緩存;不要濫用,因為離屏渲染的緩存大小只有屏幕尺寸的 2.5 倍大;當離線渲染的內容超過 100 ms 沒有使用將會被清除。應該在以下場景中才開啟該屬性:
- 繪制代價很大的靜態內容
- 結構非常復雜的視圖
要注意,離屏渲染的計算代價是很大的,與之相比,通過普通手段顯示內容要廉價很多(有很多術語由于沒有鋪墊寫出來只會更讓人困惑,還是推薦看開頭的文章)。只有當屏幕中的圖層不變時才可以利用這個選項來優化。因此,在一般情況下,還是不要開啟離屏渲染的好。
2.Group Opacity
如果一個 layer 的 opacity 值小于 1.0 并且該 layer 含有子 layer 或者有背景圖像,開啟groupopacity 將會觸發離屏渲染。建議一直關閉該功能,將 layer 的 allowsGroupOpacity 設置為 NO。
Profiling Tool:
Xcode 套件中的 Instruments 工具估計是最沒有被有效利用的工具之一,我以前就只用來查看內存占用以及泄露問題了。實際上利用 Instruments來對視圖和動畫性能調優是非常高效的。視圖和動畫的性能一旦有問題自然是非常不爽快的,相對于手工一遍遍主觀調試,Instruments 能夠直接指出性能不佳的部分,一目了然。
(Instruments 的文檔令人發指,基本上找不到主講工程師在視頻中提到的 debug options)
主講的工程師在介紹工具之前給出了性能調優的檢查選項:
Core Animation Instrument
在 Xcode 的菜單欄中依次選擇 Product->Profile 后,會啟動 Instruments 工具,有多種分析和調試工具,選擇 Core Animation。
根據視頻內容探索了以下調試選項:
-
Color blended layers
屏幕上綠色的部分表示該處的 layer 是 opaque (不透明的),這樣 GPU 在合成時就不需要考慮該 layer 下面的內容直接輸出該 layer 的內容就夠了;紅色的部分表示該處的內容需要 blend(混合),GPU 需要將該 layer 以及該 layer 下方的內容進行混合后才能輸出,工作量大。
IMG_0384.PNG - Color Hits Green and Misses Red
該選項與前面提到的Rasterization有關。綠色表明離屏渲染的 cache 中還有該部分的緩存,紅色表示該部分的緩存已被移除。 - Color Copied Images
前面提到過,圖像要被解碼后才能用于顯示,GPU只支持對 JPEG 和 PNG 格式,其他格式的圖像需要由 CPU 來轉化解碼,最好放在后臺中解碼。
我在使用這個選項的時候未發現畫面有任何變化,嘗試了瀏覽 gif 也沒有發現異樣。看來需要使用這個選項的場景太少。 - Color MisalignedImages
找出對字節沒有對齊的圖像并進行著色。當圖像尺寸與其容器 View 的尺寸不一樣的時候,需要把該圖像進行縮放。 - Color Offscreen-Renderd Yellow
將離屏渲染的部分標記為黃色,查找出觸發離屏渲染的部分。 - Color Compositing Fast-Path Blue
將由顯示硬件(原話為 display hardware)進行混合的 layers 標記為藍色,這是個好事,因為這意味著 GPU 的工作更少。 - Flash Updated Regions
標記屏幕上正在刷新的內容為黃色。
看完這部分以后進行性能調優不用到處猜瓶頸所在了,直接使用工具查看。
OpenGL ES Driver Instruments
在 Xcode 6.3 里,這個組件是找不到的,應該是改名成 GPU Driver 了。這個組件可以用來統計大部分的運行參數:CPU 占用,視圖幀率,渲染使用,設備使用以及更多參數。我開發中的 App 的幀率還沒有超過 50 的,真是慘不忍睹。該組件可以回答性能優化列表「Performance Investigation Mindset」中前面三個問題。
以前很少使用 Instruments,也因為我目前做的東西很少需要使用這些工具,當然還有可能是因為不知道能做什么所以沒法用,惡性循環。
View Debugging
Xcode 6 的新特性之一,可以在 Xcode 里實時查看 UI 結構了,但只支持通過 Xcode 運行的 app。相比大名鼎鼎的 Reveal 還是有不少差距的。
當然,兩者的定位不一樣。目前來說對于調試夠用了。
選擇調試工具
Case Study
案例學習這一塊給出了兩個常見的性能隱患:
1.陰影繪制
以前的常見代碼:
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)
//設置了上面這些屬性后就能繪制陰影了,這樣一來Core Animation 必須知道陰影的形狀才能進行繪制,但這樣就必須使用離屏渲染來渲染內容。(為啥,不明白)下面的方法可以避免離屏渲染。
imageViewLayer.shadowPath = CGPathCreateWithRect(imageRect, NULL)
2.圓角繪制
以往的常見代碼:
CALayer *imageViewLayer = cell.imageView.layer;
imageViewLayer.cornerRadius = imageHeight / 2.0;
imageViewLayer.masksToBounds = YES;
在給出的例子里,工程師使用一個 TableView 顯示一些圓形頭像。(由于說到關鍵時刻字幕消失了我聽不懂了,所以不明白為什么這里產生了離屏渲染)我的理解是,由于重用機制,被重用的 Cell 每一次出現,Core Animation 都要為 Cell 繪制圓角,這種機制導致了離屏繪制。
工程師的建議是:
- 不要對重用的 Cell 使用 mask。
- 如果做不到上一點,嘗試這個方法來:
1.將 TableView 的背景設置為 solid white;
2.在縮略圖上方繪制一個圓形,圓形外圍為白色;
這樣做減少了離屏渲染卻也增加了混合兩個圖層的工作,但從性能上來說依然比原來好。
總結:
離屏渲染代價昂貴,盡量避免
1·利用工具 CA Instrument 來找出它們
2·知道怎么做來避免它們(上面的 shadowPath 就是一個例子)(這個比較上,還沒搞清楚離屏渲染到底怎么觸發的)
在不同的設備上測試性能
1·使用 OpenGL ES Driver Instrument 來觀察 GPU
2·使用 Time Profiler Instrument 來觀察 CPU
知道視圖的結構和任何隱含的繪制成本
1·這個對于 table cells 和滾動比較重要