UIKit
UIKit
是iOS開發最常用的框架,可以通過設定UIKit
元件的佈局以及相關屬性來繪製介面。
事實上,UIKit
自身並不具備在螢幕成像的能力,其主要負責對使用者操作事件的響應(UIView
繼承自UIResponder
),事件響應的傳遞大體是經過逐層的檢視樹遍歷實現的。
Core Animation
Core Animation
源自於Layer Kit
,動畫只是Core Animation
的冰山一角。
Core Animation
是一個複合引擎,其職責是儘可能快地組合螢幕上不同的可視內容,這些可視內容可被分解成獨立的圖層(即CALayer),這些圖層會被儲存在一個叫做圖層樹的體系之中。從本質上而言,CALayer
是使用者所能在螢幕上看見的一切的基礎。
Core Graphics
Core Graphics
基於Quartz
高階繪圖引擎,主要用於執行時繪製影像。開發者可以使用此框架來處理基於路徑的繪圖,轉換,顏色管理,離屏渲染,圖案,漸變和陰影,影像資料管理,影像建立和影像遮罩以及PDF文件建立,顯示和分析。
當開發者需要在執行時建立影像時,可以使用Core Graphics
去繪製。與之相對的是執行前建立影像,例如用Photoshop提前做好圖片素材直接匯入應用。相比之下,我們更需要Core Graphics
去在執行時實時計算、繪製一系列影像幀來實現動畫。
Core Image
Core Image
與Core Graphics
恰恰相反,Core Graphics
用於在執行時建立影像,而Core Image
用於處理執行前建立的影像。Core Image
框架擁有一系列現成的影像過濾器,能對一寸照的影像進行高效的處理。
大部分情況下,Core Image
會在GPU中完成工作,如果GPU忙,會使用CPU進行處理。
OpenGL ES
OpenGL ES
是OpenGL
的子集。在圖形渲染原理一文中提到過OpenGL
是一套第三方標準,函式的內部實現由對應的GPU廠商開發實現。OpenGL / OpenGL ES
入門篇,請參考OpenGL/OpenGL ES 入門:圖形API以及專業名詞解析等系列文章
UIView與CALayer的關係
CALayer
事實上是使用者所能在螢幕上看見的一切的基礎。為什麼UIKit
中的檢視能夠呈現視覺化內容,就是因為UIKit
中的每一個UI檢視控制元件其實內部都有一個關聯的CALayer
,即backing layer
。
由於這種一一對應的關係,檢視層級有用檢視樹的樹形結構,對應CALayer
層級也擁有圖層樹的樹形結構。
其中,檢視的職責是建立並管理圖層,以確保當子檢視在層級關係中新增或被移除時,其關聯的圖層在圖層樹中也有相同的操作,即保證檢視樹和圖層樹在結構上的一致性。
為什麼iOS要基於UIView和CALayer提供兩個平行的層級關係呢?
其原因在於要做職責分離,這樣也能避免很多重複程式碼。在iOS和Mac OSX兩個平臺上,事件和使用者互動有很多地方的不同,基於多點觸控的使用者介面和基於滑鼠鍵盤的互動有著本質的區別,這就是為什麼iOS有UIKit
和UIView
,對應Mac OSX有AppKit
和NSView
的原因。它們在功能上很相似,但是在實現上有著顯著的區別。
實際上,這裡並不是兩個層級關係,而是四個。每一個都扮演著不同的角色。除了檢視樹和圖層樹,還有呈現樹和渲染樹。
CALayer
那麼為什麼CALayer
可以呈現視覺化內容呢?因為CALayer
基本等同於一個紋理。紋理是GPU進行影像渲染的重要依據。
在圖形渲染原理中提到紋理本質上就是一張圖片,因此CALayer
也包含一個contents
屬性指向一塊快取區,稱為backing store
,可以存放點陣圖(Bitmap)。iOS中將該快取區儲存的圖片稱為寄宿圖。
圖形渲染流水線支援從頂點開始進行繪製(在流水線中,頂點會被處理生成紋理),也支援直接使用紋理(圖片)進行渲染。相應地,在實際開發中,繪製介面也有兩種方式: 一種是手動繪製;另一種是使用圖片。
對此,iOS中也有兩種相應的實現方式:
- 使用圖片:contents image
- 手動繪製:custom drawing
Contents Image
Contents Image
是指通過CALayer
的contents
屬性來配置圖片。然而,contents
屬性的型別為id
,在這種情況下,可以給contents
屬性賦予任何值,app仍可以編譯通過。但是在實踐中,如果contents
的值不是CGImage
,得到的圖層將是空白的。
既然如此,為什麼要將contents
的屬性型別定義為id
而非CGImage
。因為在Mac OS系統中,該屬性對CGImage
和NSImage
型別的值都起作用,而在iOS系統中,該屬性只對CGImage
起作用。
本質上,contents
屬性指向的一塊快取區域,稱為backing store
,可以存放bitmap資料。
Custom Drawing
Custom Drawing
是指使用Core Graphics
直接繪製寄宿圖。實際開發中,一般通過繼承UIView
並實現-drawRect:
方法來自定義繪製。
雖然-drawRect:
是一個UIView
方法,但事實上都是底層的CALayer
完成了重繪工作並儲存了產生的圖片。
下圖所示為drawRect:
繪製定義寄宿圖的基本原理
UIView
有一個關聯圖層,即CALayer
。CALayer
有一個可選的delegate
屬性,實現了CALayerDelegate
協議。UIView
作為CALayer
的代理實現了CALayerDelegate
協議。- 當需要重繪時,即呼叫
-drawRect:
,CALayer
請求其代理給予一個寄宿圖來顯示。 CALayer
首先會嘗試呼叫-displayLayer:
方法,此時代理可以直接設定contents
屬性。
- (void)displayLayer:(CALayer *)layer;
複製程式碼
- 如果代理沒有實現
-displayLayer:
方法,CALayer
則會嘗試呼叫-drawLayer:inContext:
方法。在呼叫該方法前,CALayer
會建立一個空的寄宿圖(尺寸由bounds
和contentScale
決定)和一個Core Graphics
的繪製上下文,為繪製寄宿圖做準備,作為ctx
引數傳入。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
複製程式碼
- 最後,有
Core Graphics
繪製生成的寄宿圖會存入backing store
。
Core Animation 流水線
介紹一下Core Animation
流水線的工作原理:
Render Server
程式。
App通過IPC將渲染任務及相關資料提交給Render Server
。Render Server
處理完資料後,再傳遞至GPU。最後由GPU呼叫iOS的影像裝置進行顯示。
Core Animation
流水線的詳細過程如下:
- 首先,由app處理事件(Handle Events),如:使用者點選操作,在此過程中app可能需要更新檢視樹,相應地,圖層樹也會被更新。
- 其次,app通過CPU完成對顯示內容的計算,如:檢視的建立、佈局計算、圖片解碼、文字繪製等。在完成對現實內容的計算之後,app對圖層進行打包,並在下一次RunLoop時將其傳送至
Render Server
,即完成了一次commit Transaction
操作。 Render Server
主要執行OpenGL、Core Graphics相關程式,並呼叫GPU。- GPU則在物理層上完成了對影像的渲染。
- 最終,GPU通過
Frame Buffer
、視訊控制器等相關部件,將影像顯示在螢幕上。
對上述步驟進行串聯,他們執行所消耗的時間圓圓超過16.67ms,因此為了滿足對螢幕的60FPS重新整理率的支援,需要將這些步驟進行分解,通過流水線的方式並行執行,如下圖:
Commit Transaction
在Core Animation
流水線中,app呼叫Render Server
前的最後一步Commit Transaction
其實可以細分為4個步驟:
Layout
Display
Prepare
Commit
Layout
Layout
階段主要進行檢視構建,包括:LayoutSubviews
方法的過載,addSubview:
方法填充子檢視等。
Display
Display
階段主要進行檢視繪製,這裡僅僅是設定成像的圖後設資料。過載檢視的drawRect:
方法可以自定義UIView
的顯示,其原理是在drawRect:
方法內部繪製寄宿圖,該過程使用GPU和記憶體。
Prepare
Prepare
階段屬於附加步驟,一般處理影像的解碼和轉碼等操作。
Commit
commit
階段主要將圖層進行打包,並將它們傳送至Render Server
。該過程會遞迴執行,因為圖層和檢視都是以樹形結構存在。
動畫渲染原理
iOS動畫的渲染也是基於上述Core Animation
流水線完成的。這裡我們重點關注app與Render Server
的執行流程。
日常開發中,如果不是特別的複雜動畫,一般使用UIView
Animation實現,iOS將其處理過程分為如下三部階段:
- Step1:呼叫
animationWithDuration:animations:
方法 - Step2:在Animation Block中進行
Layout,Display,Prepare,Commit
等步驟。 - Step3:
Render Server
根據Animation逐幀進行渲染。
參考部落格 iOS影像渲染原理