一、圖形渲染技術(shù)棧
下圖所示為 iOS App 的圖形渲染技術(shù)棧仪际,App 使用 Core Graphics乏悄、Core Animation、Core Image 等框架來繪制可視化內(nèi)容羔巢,這些軟件框架相互之間也有著依賴關(guān)系。這些框架都需要通過 OpenGL 來調(diào)用 GPU 進(jìn)行繪制罩阵,最終將內(nèi)容顯示到屏幕之上竿秆。
iOS 渲染框架
UIKit
UIKit 是 iOS 開發(fā)者最常用的框架,可以通過設(shè)置 UIKit 組件的布局以及相關(guān)屬性來繪制界面稿壁。
事實(shí)上幽钢, UIKit 自身并不具備在屏幕成像的能力,其主要負(fù)責(zé)對用戶操作事件的響應(yīng)(UIView 繼承自 UIResponder)傅是,事件響應(yīng)的傳遞大體是經(jīng)過逐層的 視圖樹 遍歷實(shí)現(xiàn)的匪燕。
Core Graphics
Core Graphics 基于 Quartz 高級繪圖引擎,主要用于運(yùn)行時繪制圖像喧笔。開發(fā)者可以使用此框架來處理基于路徑的繪圖帽驯,轉(zhuǎn)換,顏色管理书闸,離屏渲染尼变,圖案,漸變和陰影浆劲,圖像數(shù)據(jù)管理嫌术,圖像創(chuàng)建和圖像遮罩以及 PDF 文檔創(chuàng)建,顯示和分析牌借。
當(dāng)開發(fā)者需要在 運(yùn)行時創(chuàng)建圖像 時度气,可以使用 Core Graphics 去繪制。與之相對的是 運(yùn)行前創(chuàng)建圖像膨报,例如用 Photoshop 提前做好圖片素材直接導(dǎo)入應(yīng)用磷籍。相比之下哲虾,我們更需要 Core Graphics 去在運(yùn)行時實(shí)時計(jì)算、繪制一系列圖像幀來實(shí)現(xiàn)動畫择示。
Core Image
Core Image 與 Core Graphics 恰恰相反束凑,Core Graphics 用于在 運(yùn)行時創(chuàng)建圖像,而 Core Image 是用來處理 運(yùn)行前創(chuàng)建的圖像 的栅盲。Core Image 框架擁有一系列現(xiàn)成的圖像過濾器汪诉,能對已存在的圖像進(jìn)行高效的處理。
大部分情況下谈秫,Core Image 會在 GPU 中完成工作扒寄,但如果 GPU 忙,會使用 CPU 進(jìn)行處理拟烫。
OpenGL ES
OpenGL ES(OpenGL for Embedded Systems该编,簡稱 GLES),是 OpenGL 的子集硕淑。在前面的 圖形渲染原理綜述 一文中提到過 OpenGL 是一套第三方標(biāo)準(zhǔn)课竣,函數(shù)的內(nèi)部實(shí)現(xiàn)由對應(yīng)的 GPU 廠商開發(fā)實(shí)現(xiàn)。
Metal
Metal 類似于 OpenGL ES置媳,也是一套第三方標(biāo)準(zhǔn)于樟,具體實(shí)現(xiàn)由蘋果實(shí)現(xiàn)。大多數(shù)開發(fā)者都沒有直接使用過 Metal拇囊,但其實(shí)所有開發(fā)者都在間接地使用 Metal迂曲。Core Animation、Core Image寥袭、SceneKit路捧、SpriteKit 等等渲染框架都是構(gòu)建于 Metal 之上的。
當(dāng)在真機(jī)上調(diào)試 OpenGL 程序時传黄,控制臺會打印出啟用 Metal 的日志杰扫。根據(jù)這一點(diǎn)可以猜測,Apple 已經(jīng)實(shí)現(xiàn)了一套機(jī)制將 OpenGL 命令無縫橋接到 Metal 上尝江,由 Metal 擔(dān)任真正于硬件交互的工作涉波。
UIView 與 CALayer 的關(guān)系
在前面的 Core Animation 簡介中提到 CALayer 事實(shí)上是用戶所能在屏幕上看見的一切的基礎(chǔ)英上。為什么 UIKit 中的視圖能夠呈現(xiàn)可視化內(nèi)容炭序?就是因?yàn)?UIKit 中的每一個 UI 視圖控件其實(shí)內(nèi)部都有一個關(guān)聯(lián)的 CALayer,即 backing layer苍日。
由于這種一一對應(yīng)的關(guān)系惭聂,視圖層級擁有 視圖樹 的樹形結(jié)構(gòu),對應(yīng) CALayer 層級也擁有 圖層樹 的樹形結(jié)構(gòu)相恃。
那么為什么 iOS 要基于 UIView 和 CALayer 提供兩個平行的層級關(guān)系呢辜纲?
其原因在于要做 職責(zé)分離,這樣也能避免很多重復(fù)代碼。在 iOS 和 Mac OS X 兩個平臺上耕腾,事件和用戶交互有很多地方的不同见剩,基于多點(diǎn)觸控的用戶界面和基于鼠標(biāo)鍵盤的交互有著本質(zhì)的區(qū)別,這就是為什么 iOS 有 UIKit 和 UIView扫俺,對應(yīng) Mac OS X 有 AppKit 和 NSView 的原因苍苞。它們在功能上很相似,但是在實(shí)現(xiàn)上有著顯著的區(qū)別狼纬。
實(shí)際上羹呵,這里并不是兩個層級關(guān)系,而是四個疗琉。每一個都扮演著不同的角色冈欢。除了 視圖樹 和 圖層樹,還有 呈現(xiàn)樹 和 渲染樹盈简。
CALayer
那么為什么 CALayer
可以呈現(xiàn)可視化內(nèi)容呢凑耻?因?yàn)?CALayer
基本等同于一個 紋理。紋理是 GPU 進(jìn)行圖像渲染的重要依據(jù)柠贤。
在 圖形渲染原理 中提到紋理本質(zhì)上就是一張圖片拳话,因此 CALayer
也包含一個 contents
屬性指向一塊緩存區(qū),稱為 backing store
种吸,可以存放位圖(Bitmap)弃衍。iOS 中將該緩存區(qū)保存的圖片稱為 寄宿圖。
圖形渲染流水線支持從頂點(diǎn)開始進(jìn)行繪制(在流水線中坚俗,頂點(diǎn)會被處理生成紋理)镜盯,也支持直接使用紋理(圖片)進(jìn)行渲染。相應(yīng)地猖败,在實(shí)際開發(fā)中速缆,繪制界面也有兩種方式:一種是 手動繪制;另一種是 使用圖片恩闻。
對此艺糜,iOS 中也有兩種相應(yīng)的實(shí)現(xiàn)方式:
使用圖片:contents image
手動繪制:custom drawing
Contents Image
Contents Image 是指通過 CALayer 的 contents 屬性來配置圖片。然而幢尚,contents 屬性的類型為 id破停。在這種情況下,可以給 contents 屬性賦予任何值尉剩,app 仍可以編譯通過真慢。但是在實(shí)踐中,如果 content 的值不是 CGImage 理茎,得到的圖層將是空白的黑界。
既然如此管嬉,為什么要將 contents 的屬性類型定義為 id 而非 CGImage。這是因?yàn)樵?Mac OS 系統(tǒng)中朗鸠,該屬性對 CGImage 和 NSImage 類型的值都起作用蚯撩,而在 iOS 系統(tǒng)中,該屬性只對 CGImage 起作用烛占。
本質(zhì)上求厕,contents 屬性指向的一塊緩存區(qū)域,稱為 backing store扰楼,可以存放 bitmap 數(shù)據(jù)呀癣。
Custom Drawing
Custom Drawing 是指使用 Core Graphics 直接繪制寄宿圖。實(shí)際開發(fā)中弦赖,一般通過繼承 UIView 并實(shí)現(xiàn) -drawRect: 方法來自定義繪制项栏。
雖然 -drawRect: 是一個 UIView 方法,但事實(shí)上都是底層的 CALayer 完成了重繪工作并保存了產(chǎn)生的圖片蹬竖。下圖所示為 -drawRect: 繪制定義寄宿圖的基本原理沼沈。
UIView 有一個關(guān)聯(lián)圖層,即 CALayer币厕。
CALayer 有一個可選的 delegate 屬性列另,實(shí)現(xiàn)了 CALayerDelegate 協(xié)議。UIView 作為 CALayer 的代理實(shí)現(xiàn)了 CALayerDelegae 協(xié)議旦装。
當(dāng)需要重繪時页衙,即調(diào)用 -drawRect:,CALayer 請求其代理給予一個寄宿圖來顯示阴绢。
CALayer 首先會嘗試調(diào)用 -displayLayer: 方法店乐,此時代理可以直接設(shè)置 contents 屬性。
- (void)displayLayer:(CALayer *)layer;
如果代理沒有實(shí)現(xiàn) -displayLayer: 方法呻袭,CALayer 則會嘗試調(diào)用 -drawLayer:inContext: 方法眨八。在調(diào)用該方法前,CALayer 會創(chuàng)建一個空的寄宿圖(尺寸由 bounds 和 contentScale 決定)和一個 Core Graphics 的繪制上下文左电,為繪制寄宿圖做準(zhǔn)備廉侧,作為 ctx 參數(shù)傳入。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
最后篓足,由 Core Graphics 繪制生成的寄宿圖會存入 backing store段誊。
二、Core Animation 流水線
通過前面的介紹纷纫,我們知道了 CALayer 的本質(zhì)枕扫,那么它是如何調(diào)用 GPU 并顯示可視化內(nèi)容的呢陪腌?下面我們就需要介紹一下 Core Animation 流水線的工作原理辱魁。
事實(shí)上烟瞧,app 本身并不負(fù)責(zé)渲染,渲染則是由一個獨(dú)立的進(jìn)程負(fù)責(zé)染簇,即 Render Server 進(jìn)程参滴。
App 通過 IPC 將渲染任務(wù)及相關(guān)數(shù)據(jù)提交給 Render Server。Render Server 處理完數(shù)據(jù)后锻弓,再傳遞至 GPU砾赔。最后由 GPU 調(diào)用 iOS 的圖像設(shè)備進(jìn)行顯示。
Core Animation 流水線的詳細(xì)過程如下:
首先青灼,由 app 處理事件(Handle Events)暴心,如:用戶的點(diǎn)擊操作,在此過程中 app 可能需要更新 視圖樹杂拨,相應(yīng)地专普,圖層樹 也會被更新。
其次弹沽,app 通過 CPU 完成對顯示內(nèi)容的計(jì)算檀夹,如:視圖的創(chuàng)建、布局計(jì)算策橘、圖片解碼炸渡、文本繪制等。在完成對顯示內(nèi)容的計(jì)算之后丽已,app 對圖層進(jìn)行打包蚌堵,并在下一次 RunLoop 時將其發(fā)送至 Render Server,即完成了一次 Commit Transaction 操作沛婴。
Render Server 主要執(zhí)行 Open GL辰斋、Core Graphics 相關(guān)程序,并調(diào)用 GPU
GPU 則在物理層上完成了對圖像的渲染瘸味。
最終宫仗,GPU 通過 Frame Buffer、視頻控制器等相關(guān)部件旁仿,將圖像顯示在屏幕上藕夫。
對上述步驟進(jìn)行串聯(lián),它們執(zhí)行所消耗的時間遠(yuǎn)遠(yuǎn)超過 16.67 ms枯冈,因此為了滿足對屏幕的 60 FPS 刷新率的支持毅贮,需要將這些步驟進(jìn)行分解,通過流水線的方式進(jìn)行并行執(zhí)行尘奏,如下圖所示滩褥。
Commit Transaction
在 Core Animation 流水線中,app 調(diào)用 Render Server 前的最后一步 Commit Transaction 其實(shí)可以細(xì)分為 4 個步驟:
Layout
Display
Prepare
Commit
Layout
Layout
階段主要進(jìn)行視圖構(gòu)建炫加,包括:LayoutSubviews
方法的重載瑰煎,addSubview:
方法填充子視圖等铺然。
Display
Display
階段主要進(jìn)行視圖繪制,這里僅僅是設(shè)置最要成像的圖元數(shù)據(jù)酒甸。重載視圖的 drawRect:
方法可以自定義 UIView
的顯示魄健,其原理是在 drawRect:
方法內(nèi)部繪制寄宿圖,該過程使用 CPU 和內(nèi)存插勤。
Prepare
Prepare
階段屬于附加步驟沽瘦,一般處理圖像的解碼和轉(zhuǎn)換等操作。
Commit
Commit
階段主要將圖層進(jìn)行打包农尖,并將它們發(fā)送至 Render Server
析恋。該過程會遞歸執(zhí)行,因?yàn)閳D層和視圖都是以樹形結(jié)構(gòu)存在盛卡。
三绿满、動畫渲染原理
iOS 動畫的渲染也是基于上述 Core Animation 流水線完成的。這里我們重點(diǎn)關(guān)注 app 與 Render Server 的執(zhí)行流程窟扑。
日常開發(fā)中喇颁,如果不是特別復(fù)雜的動畫,一般使用 UIView Animation 實(shí)現(xiàn)嚎货,iOS 將其處理過程分為如下三部階段:
Step 1:調(diào)用 animationWithDuration:animations: 方法
Step 2:在 Animation Block 中進(jìn)行 Layout橘霎,Display,Prepare殖属,Commit 等步驟姐叁。
Step 3:Render Server 根據(jù) Animation 逐幀進(jìn)行渲染。