AsyncDisplayKit
AsyncDisplayKit 是 Facebook 開源的一個(gè)用于保持 iOS 界面流暢的庫(kù)采呐,我從中學(xué)到了很多東西蹬叭,所以下面我會(huì)花較大的篇幅來(lái)對(duì)其進(jìn)行介紹和分析服球。
ASDK 的由來(lái)
ASDK 的作者是 Scott Goodson (Linkedin)挖诸,
他曾經(jīng)在蘋果工作任柜,負(fù)責(zé) iOS 的一些內(nèi)置應(yīng)用的開發(fā),比如股票吨灭、計(jì)算器刚照、地圖、鐘表沃于、設(shè)置涩咖、Safari 等海诲,當(dāng)然他也參與了 UIKit framework 的開發(fā)繁莹。后來(lái)他加入 Facebook 后,負(fù)責(zé) Paper 的開發(fā)特幔,創(chuàng)建并開源了 AsyncDisplayKit咨演。目前他在 Pinterest 和 Instagram 負(fù)責(zé) iOS 開發(fā)和用戶體驗(yàn)的提升等工作。
ASDK 自 2014 年 6 月開源蚯斯,10 月發(fā)布 1.0 版薄风。目前 ASDK 即將要發(fā)布 2.0 版。
V2.0 增加了更多布局相關(guān)的代碼拍嵌,ComponentKit 團(tuán)隊(duì)為此貢獻(xiàn)很多遭赂。
現(xiàn)在 Github 的 master 分支上的版本是 V1.9.1,已經(jīng)包含了 V2.0 的全部?jī)?nèi)容横辆。
ASDK 的資料
想要了解 ASDK 的原理和細(xì)節(jié)撇他,最好從下面幾個(gè)視頻開始:
2014.10.15 NSLondon – Scott Goodson – Behind AsyncDisplayKit
2015.03.02 MCE 2015 – Scott Goodson – Effortless Responsiveness with AsyncDisplayKit
2015.10.25 AsyncDisplayKit 2.0: Intelligent User Interfaces – NSSpain 2015
前兩個(gè)視頻內(nèi)容大同小異,都是介紹 ASDK 的基本原理狈蚤,附帶介紹 POP 等其他項(xiàng)目困肩。
后一個(gè)視頻增加了 ASDK 2.0 的新特性的介紹。
除此之外脆侮,還可以到 Github Issues 里看一下 ASDK 相關(guān)的討論锌畸,下面是幾個(gè)比較重要的內(nèi)容:
關(guān)于 Runloop Dispatch
關(guān)于 ComponentKit 和 ASDK 的區(qū)別
為什么不支持 Storyboard 和 Autolayout
如何評(píng)測(cè)界面的流暢度
之后,還可以到 Google Groups 來(lái)查看和討論更多內(nèi)容:
https://groups.google.com/forum/#!forum/asyncdisplaykit
ASDK 的基本原理
ASDK 認(rèn)為靖避,阻塞主線程的任務(wù)潭枣,主要分為上面這三大類比默。文本和布局的計(jì)算、渲染卸耘、解碼退敦、繪制都可以通過各種方式異步執(zhí)行,但 UIKit 和 Core Animation 相關(guān)操作必需在主線程進(jìn)行蚣抗。ASDK 的目標(biāo)侈百,就是盡量把這些任務(wù)從主線程挪走,而挪不走的翰铡,就盡量?jī)?yōu)化性能钝域。
為了達(dá)成這一目標(biāo),ASDK 嘗試對(duì) UIKit 組件進(jìn)行封裝:
這是常見的 UIView 和 CALayer 的關(guān)系:View 持有 Layer 用于顯示锭魔,View 中大部分顯示屬性實(shí)際是從 Layer 映射而來(lái)例证;Layer 的 delegate 在這里是 View,當(dāng)其屬性改變迷捧、動(dòng)畫產(chǎn)生時(shí)织咧,View 能夠得到通知。UIView 和 CALayer 不是線程安全的漠秋,并且只能在主線程創(chuàng)建笙蒙、訪問和銷毀。
ASDK 為此創(chuàng)建了 ASDisplayNode 類庆锦,包裝了常見的視圖屬性(比如 frame/bounds/alpha/transform/backgroundColor/superNode/subNodes 等)捅位,然后它用 UIView->CALayer 相同的方式,實(shí)現(xiàn)了 ASNode->UIView 這樣一個(gè)關(guān)系搂抒。
當(dāng)不需要響應(yīng)觸摸事件時(shí)艇搀,ASDisplayNode 可以被設(shè)置為 layer backed,即 ASDisplayNode 充當(dāng)了原來(lái) UIView 的功能求晶,節(jié)省了更多資源焰雕。
與 UIView 和 CALayer 不同,ASDisplayNode 是線程安全的芳杏,它可以在后臺(tái)線程創(chuàng)建和修改矩屁。Node 剛創(chuàng)建時(shí),并不會(huì)在內(nèi)部新建 UIView 和 CALayer蚜锨,直到第一次在主線程訪問 view 或 layer 屬性時(shí)档插,它才會(huì)在內(nèi)部生成對(duì)應(yīng)的對(duì)象。當(dāng)它的屬性(比如frame/transform)改變后亚再,它并不會(huì)立刻同步到其持有的 view 或 layer 去郭膛,而是把被改變的屬性保存到內(nèi)部的一個(gè)中間變量,稍后在需要時(shí)氛悬,再通過某個(gè)機(jī)制一次性設(shè)置到內(nèi)部的 view 或 layer则剃。
通過模擬和封裝 UIView/CALayer耘柱,開發(fā)者可以把代碼中的 UIView 替換為 ASNode,很大的降低了開發(fā)和學(xué)習(xí)成本棍现,同時(shí)能獲得 ASDK 底層大量的性能優(yōu)化调煎。為了方便使用, ASDK 把大量常用控件都封裝成了 ASNode 的子類己肮,比如 Button士袄、Control、Cell谎僻、Image娄柳、ImageView、Text艘绍、TableView赤拒、CollectionView 等。利用這些控件诱鞠,開發(fā)者可以盡量避免直接使用 UIKit 相關(guān)控件挎挖,以獲得更完整的性能提升。
ASDK 的圖層預(yù)合成
有時(shí)一個(gè) layer 會(huì)包含很多 sub-layer航夺,而這些 sub-layer 并不需要響應(yīng)觸摸事件蕉朵,也不需要進(jìn)行動(dòng)畫和位置調(diào)整。ASDK 為此實(shí)現(xiàn)了一個(gè)被稱為 pre-composing 的技術(shù)敷存,可以把這些 sub-layer 合成渲染為一張圖片墓造。開發(fā)時(shí)堪伍,ASNode 已經(jīng)替代了 UIView 和 CALayer锚烦;直接使用各種 Node 控件并設(shè)置為 layer backed 后,ASNode 甚至可以通過預(yù)合成來(lái)避免創(chuàng)建內(nèi)部的 UIView 和 CALayer帝雇。
通過這種方式涮俄,把一個(gè)大的層級(jí),通過一個(gè)大的繪制方法繪制到一張圖上尸闸,性能會(huì)獲得很大提升彻亲。CPU 避免了創(chuàng)建 UIKit 對(duì)象的資源消耗,GPU 避免了多張 texture 合成和渲染的消耗吮廉,更少的 bitmap 也意味著更少的內(nèi)存占用苞尝。
ASDK 異步并發(fā)操作
自 iPhone 4S 起,iDevice 已經(jīng)都是雙核 CPU 了宦芦,現(xiàn)在的 iPad 甚至已經(jīng)更新到 3 核了宙址。充分利用多核的優(yōu)勢(shì)、并發(fā)執(zhí)行任務(wù)對(duì)保持界面流暢有很大作用调卑。ASDK 把布局計(jì)算抡砂、文本排版大咱、圖片/文本/圖形渲染等操作都封裝成較小的任務(wù),并利用 GCD 異步并發(fā)執(zhí)行注益。如果開發(fā)者使用了 ASNode 相關(guān)的控件碴巾,那么這些并發(fā)操作會(huì)自動(dòng)在后臺(tái)進(jìn)行,無(wú)需進(jìn)行過多配置丑搔。
Runloop 任務(wù)分發(fā)
Runloop work distribution 是 ASDK 比較核心的一個(gè)技術(shù)厦瓢,ASDK 的介紹視頻和文檔中都沒有詳細(xì)展開介紹,所以這里我會(huì)多做一些分析啤月。如果你對(duì) Runloop 還不太了解旷痕,可以看一下我之前的文章深入理解RunLoop,里面對(duì) ASDK 也有所提及顽冶。
iOS 的顯示系統(tǒng)是由 VSync 信號(hào)驅(qū)動(dòng)的欺抗,VSync 信號(hào)由硬件時(shí)鐘生成,每秒鐘發(fā)出 60 次(這個(gè)值取決設(shè)備硬件强重,比如 iPhone 真機(jī)上通常是 59.97)绞呈。iOS 圖形服務(wù)接收到 VSync 信號(hào)后,會(huì)通過 IPC 通知到 App 內(nèi)间景。App 的 Runloop 在啟動(dòng)后會(huì)注冊(cè)對(duì)應(yīng)的 CFRunLoopSource 通過 mach_port 接收傳過來(lái)的時(shí)鐘信號(hào)通知佃声,隨后 Source 的回調(diào)會(huì)驅(qū)動(dòng)整個(gè) App 的動(dòng)畫與顯示。
Core Animation 在 RunLoop 中注冊(cè)了一個(gè) Observer倘要,監(jiān)聽了 BeforeWaiting 和 Exit 事件圾亏。這個(gè) Observer 的優(yōu)先級(jí)是 2000000,低于常見的其他 Observer封拧。當(dāng)一個(gè)觸摸事件到來(lái)時(shí)志鹃,RunLoop 被喚醒,App 中的代碼會(huì)執(zhí)行一些操作泽西,比如創(chuàng)建和調(diào)整視圖層級(jí)曹铃、設(shè)置 UIView 的 frame、修改 CALayer 的透明度捧杉、為視圖添加一個(gè)動(dòng)畫陕见;這些操作最終都會(huì)被 CALayer 捕獲,并通過 CATransaction 提交到一個(gè)中間狀態(tài)去(CATransaction 的文檔略有提到這些內(nèi)容味抖,但并不完整)评甜。當(dāng)上面所有操作結(jié)束后,RunLoop 即將進(jìn)入休眠(或者退出)時(shí)仔涩,關(guān)注該事件的 Observer 都會(huì)得到通知忍坷。這時(shí) CA 注冊(cè)的那個(gè) Observer 就會(huì)在回調(diào)中,把所有的中間狀態(tài)合并提交到 GPU 去顯示;如果此處有動(dòng)畫承匣,CA 會(huì)通過 DisplayLink 等機(jī)制多次觸發(fā)相關(guān)流程蓖乘。
ASDK 在此處模擬了 Core Animation 的這個(gè)機(jī)制:所有針對(duì) ASNode 的修改和提交,總有些任務(wù)是必需放入主線程執(zhí)行的韧骗。當(dāng)出現(xiàn)這種任務(wù)時(shí)嘉抒,ASNode 會(huì)把任務(wù)用 ASAsyncTransaction(Group) 封裝并提交到一個(gè)全局的容器去。ASDK 也在 RunLoop 中注冊(cè)了一個(gè) Observer袍暴,監(jiān)視的事件和 CA 一樣些侍,但優(yōu)先級(jí)比 CA 要低。當(dāng) RunLoop 進(jìn)入休眠前政模、CA 處理完事件后岗宣,ASDK 就會(huì)執(zhí)行該 loop 內(nèi)提交的所有任務(wù)。具體代碼見這個(gè)文件:ASAsyncTransactionGroup淋样。
通過這種機(jī)制耗式,ASDK 可以在合適的機(jī)會(huì)把異步、并發(fā)的操作同步到主線程去趁猴,并且能獲得不錯(cuò)的性能刊咳。
其他
ASDK 中還有封裝很多高級(jí)的功能,比如滑動(dòng)列表的預(yù)加載儡司、V2.0添加的新的布局模式等娱挨。ASDK 是一個(gè)很龐大的庫(kù),它本身并不推薦你把整個(gè) App 全部都改為 ASDK 驅(qū)動(dòng)捕犬,把最需要提升交互性能的地方用 ASDK 進(jìn)行優(yōu)化就足夠了跷坝。