這是一篇關於iOS的視圖渲染流程以及性能優化的文章,源於WWDC視頻。html
視圖渲染的處理層級圖以下:
UIKit是經常使用的框架,顯示、動畫都經過CoreAnimation;
CoreAnimation是核心動畫,依賴於OpenGL ES作GPU渲染(目前最新的iPhone已經都使用Metal,爲了和圖文一致,本文後面繼續使用OpenGL ES來描述),CoreGraphics作CPU渲染;
最底層的GraphicsHardWare是圖形硬件。git
視圖渲染的總體流程以下:
視圖渲染到屏幕上須要CPU和GPU一塊兒協做。App將一部分數據經過CoreGraphics、CoreImage調用CPU進行預處理,最終經過OpenGL ES將數據傳送到 GPU,最終顯示到屏幕。github
渲染的具體過程能夠用上圖來描述:web
其中App的Commit流程又能夠分爲Layout、Display、Prepare、Commit四個步驟。算法
調用layoutSubviews方法;
調用addSubview:方法;緩存
會形成CPU和I/O瓶頸;性能優化
經過drawRect繪製視圖;
繪製string(字符串);markdown
會形成CPU和內存瓶頸;架構
每一個UIView都有CALayer,同時圖層有一個像素存儲空間,存放視圖;調用-setNeedsDisplay
的時候,僅會設置圖層爲dirty。
當渲染系統準備就緒時會調用視圖的-display
方法,同時裝配像素存儲空間,創建一個CoreGraphics上下文(CGContextRef),將上下文push進上下文堆棧,繪圖程序進入對應的內存存儲空間。併發
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(10, 10)];
[path addLineToPoint:CGPointMake(20, 20)];
[path closePath];
path.lineWidth = 1;
[[UIColor redColor] setStroke];
[path stroke];
複製代碼
在-drawRect方法中實現如上代碼,UIKit會將自動生成的CGContextRef 放入上下文堆棧。
當繪製完成後,視圖的像素會被渲染到屏幕上;當下次再次調用視圖的-setNeedsDisplay,將會再次調用-drawRect方法。
解碼圖片;
圖片格式轉換;
當咱們使用UIImage、CGImage時,圖片並無真正解碼。iOS會先用一些基礎的圖像信息建立對象,等到真正使用時再建立bitmap並進行解碼。儘可能避免使用不支持硬解的圖片格式,好比說webp;
打包layers併發送到渲染server;
遞歸提交子樹的layers;
若是子樹太複雜,會消耗很大,對性能形成影響;
儘量簡化viewTree;
當顯示一個UIImageView時,Core Animation會建立一個OpenGL ES紋理,並確保在這個圖層中的位圖被上傳到對應的紋理中。當你重寫 -drawInContext
方法時,Core Animation會請求分配一個紋理,同時確保Core Graphics會將你在-drawInContext
中繪製的東西放入到紋理的位圖數據中。
Tiled-Based 渲染是移動設備的主流。整個屏幕會分解成N*Npixels組成的瓦片(Tiles),tiles存儲於SoC 緩存(SoC=system on chip,片上系統,是在整塊芯片上實現一個複雜系統功能,如intel cpu,整合了集顯,內存控制器,cpu運核心,緩存,隊列、非核心和I/O控制器)。 幾何形狀會分解成若干個tiles,對於每一塊tile,把必須的幾何體提交到OpenGL ES,而後進行渲染(光柵化)。完畢後,將tile的數據發送回cpu。
傳送數據是很是消耗性能的。相對來講,屢次計算比屢次發送數據更加經濟高效,可是額外的計算也會產生一些性能損耗。
PS:在移動平臺控制幀率在一個合適的水平能夠節省電能,會有效的延長電池壽命,同時會相對的提升用戶體驗。
普通的Tile-Based渲染流程以下:
一、CommandBuffer,接受OpenGL ES處理完畢的渲染指令;
二、Tiler,調用頂點着色器,把頂點數據進行分塊(Tiling);
三、ParameterBuffer,接受分塊完畢的tile和對應的渲染參數;
四、Renderer,調用片元着色器,進行像素渲染;
五、RenderBuffer,存儲渲染完畢的像素;
一、渲染layer的mask紋理,同Tile-Based的基本渲染邏輯;
二、渲染layer的content紋理,同Tile-Based的基本渲染邏輯;
三、Compositing操做,合併一、2的紋理;
使用UIBlurEffect,應該是儘量小的view,由於性能消耗巨大。
60FPS的設備,每幀只有16.67ms的時間進行處理。
因爲每一幀的頂點和像素處理相對獨立,iOS會將CPU處理,頂點處理,像素處理安排在相鄰的三幀中。如圖,當一個渲染命令提交後,要在當幀以後的第三幀,渲染結果纔會顯示出來。
把視圖的內容渲染成紋理並緩存,能夠經過CALayer的shouldRasterize屬性開啓光柵化。 注意,光柵化的元素,總大小限制爲2.5倍的屏幕。 更新內容時,會啓用離屏渲染,因此更新代價較大,只能用於靜態內容;並且若是光柵化的元素100ms沒有被使用將被移除,故而不經常使用元素的光柵化並不會優化顯示。
CALayer的allowsGroupOpacity屬性,UIView 的alpha屬性等同於 CALayer opacity屬性。
當GroupOpacity=YES
時,會先不考慮透明度,等繪製完成全部layer(自身+子layers),再統一計算透明。
假設某個視圖A有一個字視圖B,他們的alpha都是0.5(根視圖是黑色,A和B都是白色),當咱們繪製視圖的時候:
若是未開啓組透明,首先是繪製視圖A(0.5白色),而後再繪製視圖B,繪製視圖B的時候是在父視圖0.5白色和根視圖0.5黑色的基礎上疊加視圖B的0.5白色,最終就是0.75白色。
若是開啓了組透明,首先是繪製視圖A(白色),而後在A的基礎上直接繪製視圖B(白色),最終再統一計算透明0.5,因此A和B的顏色保持一致。(邊界是特地加的,爲了區分視圖B)
The default value is read from the boolean UIViewGroupOpacity property in the main bundle’s Info.plist file. If no value is found, the default value is YES for apps linked against the iOS 7 SDK or later and NO for apps linked against an earlier SDK.
爲了讓子視圖與父視圖保持一樣的透明度和優化性能,從 iOS 7 之後默認全局開啓了這個功能。對如今的開發者來講,幾乎能夠不用關注。
這個是WWDC推薦的檢查項目:
一、幀率通常在多少?
60幀每秒;(TimeProfiler工具能夠查看耗時)
二、是否存在CPU和GPU瓶頸? (查看佔有率)
更少的使用CPU和GPU能夠有效的保存電量;
三、是否額外使用CPU來進行渲染?
重寫了drawRect會致使CPU渲染;在CPU進行渲染時,GPU大多數狀況是處於等待狀態;
四、是否存在過多離屏渲染?
越少越好;離屏渲染會致使上下文切換,GPU產生idle;
五、是否渲染過多視圖?
視圖越少越好;透明度爲1的視圖更受歡迎;
六、使用奇怪的圖片格式和大小?
避免格式轉換和調整圖片大小;一個圖片若是不被GPU支持,那麼須要CPU來轉換。(Xcode有對PNG圖片進行特殊的算法優化)
七、是否使用昂貴的特效?
視圖特效存在消耗,調整合適的大小;例如前面提到的UIBlurEffect;
八、是否視圖樹上存在沒必要要的元素?
理解視圖樹上全部點的必要性,去掉沒必要要的元素;忘記remove視圖是很常見的事情,特別是當View的類比較大的時候。
以上,是8個問題對應的工具。遇到性能問題,先分析、定位問題所在,而不是埋頭鑽進代碼的海洋。
上面的作法,會致使離屏渲染;下面的作法是正確的作法。
不要使用沒必要要的mask,能夠預處理圖片爲圓形;或者添加中間爲圓形透明的白色背景視圖。即便添加額外的視圖,會致使額外的計算;但仍然會快一點,由於相對於切換上下文,GPU更擅長渲染。
離屏渲染會致使GPU利用率不到100%,幀率卻很低。(切換上下文會產生idle time)
使用instruments的CoreAnimation工具來檢查離屏渲染,黃色是咱們不但願看到的顏色。
使用真機來調試,由於模擬器使用的CALayer是OSX的CALayer,不是iOS的CALayer。若是用模擬器調試,會發現全部的視圖都是黃色。
視頻中的這一句話,讓我對iOS的視圖渲染茅塞頓開:CALayer in CA is two triangles
。
iOS對底層渲染作了很好的封裝,優秀的開發環境容許咱們直接使用UIKit進行視圖操做,使用CoreAnimation完成複雜的動畫,卻不須要知道視圖渲染的複雜過程,更不須要知道GPU的Tile-Based架構。
可是咱們的不該該知足於API的使用,更應該瞭解其背後複雜的運行規則以及設計原理。
因爲WWDC視頻內容較久,裏面仍在使用OpenGL ES(這部分如今已經由Metal替代),可是這並不影響咱們對整個移動端的視圖渲染過程進行學習。