AsyncDisplayKit

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)的提升等工作。

image.png

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 的基本原理
image.png

ASDK 認(rèn)為,阻塞主線程的任務(wù),主要分為上面這三大類。文本和布局的計(jì)算、渲染、解碼、繪制都可以通過(guò)各種方式異步執(zhí)行,但 UIKit 和 Core Animation 相關(guān)操作必需在主線程進(jìn)行。ASDK 的目標(biāo),就是盡量把這些任務(wù)從主線程挪走,而挪不走的,就盡量?jī)?yōu)化性能。

為了達(dá)成這一目標(biāo),ASDK 嘗試對(duì) UIKit 組件進(jìn)行封裝:


image.png

這是常見(jià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)建、訪問(wèn)和銷毀。


image.png

ASDK 為此創(chuàng)建了 ASDisplayNode 類,包裝了常見(jiàn)的視圖屬性(比如 frame/bounds/alpha/transform/backgroundColor/superNode/subNodes 等),然后它用 UIView->CALayer 相同的方式,實(shí)現(xiàn)了 ASNode->UIView 這樣一個(gè)關(guān)系。


image.png

當(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,直到第一次在主線程訪問(wèn) view 或 layer 屬性時(shí),它才會(huì)在內(nèi)部生成對(duì)應(yīng)的對(duì)象。當(dāng)它的屬性(比如frame/transform)改變后,它并不會(huì)立刻同步到其持有的 view 或 layer 去,而是把被改變的屬性保存到內(nèi)部的一個(gè)中間變量,稍后在需要時(shí),再通過(guò)某個(gè)機(jī)制一次性設(shè)置到內(nèi)部的 view 或 layer。

通過(guò)模擬和封裝 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ù)合成
image.png

有時(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 甚至可以通過(guò)預(yù)合成來(lái)避免創(chuàng)建內(nèi)部的 UIView 和 CALayer。

通過(guò)這種方式,把一個(gè)大的層級(jí),通過(guò)一個(gè)大的繪制方法繪制到一張圖上,性能會(huì)獲得很大提升。CPU 避免了創(chuàng)建 UIKit 對(duì)象的資源消耗,GPU 避免了多張 texture 合成和渲染的消耗,更少的 bitmap 也意味著更少的內(nèi)存占用。

ASDK 異步并發(fā)操作
image.png

自 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)行過(guò)多配置。

Runloop 任務(wù)分發(fā)

Runloop work distribution 是 ASDK 比較核心的一個(gè)技術(shù),ASDK 的介紹視頻和文檔中都沒(méi)有詳細(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ì)通過(guò) IPC 通知到 App 內(nèi)。App 的 Runloop 在啟動(dòng)后會(huì)注冊(cè)對(duì)應(yīng)的 CFRunLoopSource 通過(guò) mach_port 接收傳過(guò)來(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,低于常見(jiàn)的其他 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 捕獲,并通過(guò) 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ì)通過(guò) 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ù)。具體代碼見(jiàn)這個(gè)文件:ASAsyncTransactionGroup。

通過(guò)這種機(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)化就足夠了。

參考文章:
使用 ASDK 性能調(diào)優(yōu) - 提升 iOS 界面的渲染性能

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容