## **AsyncDisplayKit**
AsyncDisplayKit 是 Facebook 開源的一個用于保持 iOS 界面流暢的庫,我從中學到了很多東西,所以下面我會花較大的篇幅來對其進行介紹和分析。
[TOC=3,3]
### **ASDK 的由來**

ASDK 的作者是 Scott Goodson ([Linkedin](https://www.linkedin.com/in/iosengineer)),
他曾經在蘋果工作,負責 iOS 的一些內置應用的開發,比如股票、計算器、地圖、鐘表、設置、Safari 等,當然他也參與了 UIKit framework 的開發。后來他加入 Facebook 后,負責 Paper 的開發,創建并開源了 AsyncDisplayKit。目前他在 Pinterest 和 Instagram 負責 iOS 開發和用戶體驗的提升等工作。

ASDK 自 2014 年 6 月開源,10 月發布 1.0 版。目前 ASDK 即將要發布 2.0 版。
V2.0 增加了更多布局相關的代碼,ComponentKit 團隊為此貢獻很多。
現在 Github 的 master 分支上的版本是 V1.9.1,已經包含了 V2.0 的全部內容。
### **ASDK 的資料**
想要了解 ASDK 的原理和細節,最好從下面幾個視頻開始:
2014.10.15?[NSLondon - Scott Goodson - Behind AsyncDisplayKit](https://www.youtube.com/watch?v=-IPMNWqA638)
2015.03.02?[MCE 2015 - Scott Goodson - Effortless Responsiveness with AsyncDisplayKit](https://www.youtube.com/watch?v=ZPL4Nse76oY)
2015.10.25?[AsyncDisplayKit 2.0: Intelligent User Interfaces - NSSpain 2015](https://www.youtube.com/watch?v=RY_X7l1g79Q)
前兩個視頻內容大同小異,都是介紹 ASDK 的基本原理,附帶介紹 POP 等其他項目。
后一個視頻增加了 ASDK 2.0 的新特性的介紹。
除此之外,還可以到 Github Issues 里看一下 ASDK 相關的討論,下面是幾個比較重要的內容:
[關于 Runloop Dispatch](https://github.com/facebook/AsyncDisplayKit/issues/42)
[關于 ComponentKit 和 ASDK 的區別](https://github.com/facebook/AsyncDisplayKit/issues/70)
[為什么不支持 Storyboard 和 Autolayout](https://github.com/facebook/AsyncDisplayKit/issues/132)
[如何評測界面的流暢度](https://github.com/facebook/AsyncDisplayKit/issues/204)
之后,還可以到 Google Groups 來查看和討論更多內容:
[https://groups.google.com/forum/#!forum/asyncdisplaykit](https://groups.google.com/forum/#!forum/asyncdisplaykit)
### **ASDK 的基本原理**

ASDK 認為,阻塞主線程的任務,主要分為上面這三大類。文本和布局的計算、渲染、解碼、繪制都可以通過各種方式異步執行,但 UIKit 和 Core Animation 相關操作必需在主線程進行。ASDK 的目標,就是盡量把這些任務從主線程挪走,而挪不走的,就盡量優化性能。
為了達成這一目標,ASDK 嘗試對 UIKit 組件進行封裝:

這是常見的 UIView 和 CALayer 的關系:View 持有 Layer 用于顯示,View 中大部分顯示屬性實際是從 Layer 映射而來;Layer 的 delegate 在這里是 View,當其屬性改變、動畫產生時,View 能夠得到通知。UIView 和 CALayer 不是線程安全的,并且只能在主線程創建、訪問和銷毀。

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

當不需要響應觸摸事件時,ASDisplayNode 可以被設置為 layer backed,即 ASDisplayNode 充當了原來 UIView 的功能,節省了更多資源。
與 UIView 和 CALayer 不同,ASDisplayNode 是線程安全的,它可以在后臺線程創建和修改。Node 剛創建時,并不會在內部新建 UIView 和 CALayer,直到第一次在主線程訪問 view 或 layer 屬性時,它才會在內部生成對應的對象。當它的屬性(比如frame/transform)改變后,它并不會立刻同步到其持有的 view 或 layer 去,而是把被改變的屬性保存到內部的一個中間變量,稍后在需要時,再通過某個機制一次性設置到內部的 view 或 layer。
通過模擬和封裝 UIView/CALayer,開發者可以把代碼中的 UIView 替換為 ASNode,很大的降低了開發和學習成本,同時能獲得 ASDK 底層大量的性能優化。為了方便使用, ASDK 把大量常用控件都封裝成了 ASNode 的子類,比如 Button、Control、Cell、Image、ImageView、Text、TableView、CollectionView 等。利用這些控件,開發者可以盡量避免直接使用 UIKit 相關控件,以獲得更完整的性能提升。
### **ASDK 的圖層預合成**
?

有時一個 layer 會包含很多 sub-layer,而這些 sub-layer 并不需要響應觸摸事件,也不需要進行動畫和位置調整。ASDK 為此實現了一個被稱為 pre-composing 的技術,可以把這些 sub-layer 合成渲染為一張圖片。開發時,ASNode 已經替代了 UIView 和 CALayer;直接使用各種 Node 控件并設置為 layer backed 后,ASNode 甚至可以通過預合成來避免創建內部的 UIView 和 CALayer。
通過這種方式,把一個大的層級,通過一個大的繪制方法繪制到一張圖上,性能會獲得很大提升。CPU 避免了創建 UIKit 對象的資源消耗,GPU 避免了多張 texture 合成和渲染的消耗,更少的 bitmap 也意味著更少的內存占用。
### **ASDK 異步并發操作**

自 iPhone 4S 起,iDevice 已經都是雙核 CPU 了,現在的 iPad 甚至已經更新到 3 核了。充分利用多核的優勢、并發執行任務對保持界面流暢有很大作用。ASDK 把布局計算、文本排版、圖片/文本/圖形渲染等操作都封裝成較小的任務,并利用 GCD 異步并發執行。如果開發者使用了 ASNode 相關的控件,那么這些并發操作會自動在后臺進行,無需進行過多配置。
### **Runloop 任務分發**
Runloop work distribution 是 ASDK 比較核心的一個技術,ASDK 的介紹視頻和文檔中都沒有詳細展開介紹,所以這里我會多做一些分析。如果你對 Runloop 還不太了解,可以看一下我之前的文章[?深入理解RunLoop](http://blog.ibireme.com/2015/05/18/runloop/),里面對 ASDK 也有所提及。

iOS 的顯示系統是由 VSync 信號驅動的,VSync 信號由硬件時鐘生成,每秒鐘發出 60 次(這個值取決設備硬件,比如 iPhone 真機上通常是 59.97)。iOS 圖形服務接收到 VSync 信號后,會通過 IPC 通知到 App 內。App 的 Runloop 在啟動后會注冊對應的 CFRunLoopSource 通過 mach_port 接收傳過來的時鐘信號通知,隨后 Source 的回調會驅動整個 App 的動畫與顯示。
Core Animation 在 RunLoop 中注冊了一個 Observer,監聽了 BeforeWaiting 和 Exit 事件。這個 Observer 的優先級是 2000000,低于常見的其他 Observer。當一個觸摸事件到來時,RunLoop 被喚醒,App 中的代碼會執行一些操作,比如創建和調整視圖層級、設置 UIView 的 frame、修改 CALayer 的透明度、為視圖添加一個動畫;這些操作最終都會被 CALayer 捕獲,并通過 CATransaction 提交到一個中間狀態去(CATransaction 的文檔略有提到這些內容,但并不完整)。當上面所有操作結束后,RunLoop 即將進入休眠(或者退出)時,關注該事件的 Observer 都會得到通知。這時 CA 注冊的那個 Observer 就會在回調中,把所有的中間狀態合并提交到 GPU 去顯示;如果此處有動畫,CA 會通過 DisplayLink 等機制多次觸發相關流程。
ASDK 在此處模擬了 Core Animation 的這個機制:所有針對 ASNode 的修改和提交,總有些任務是必需放入主線程執行的。當出現這種任務時,ASNode 會把任務用 ASAsyncTransaction(Group) 封裝并提交到一個全局的容器去。ASDK 也在 RunLoop 中注冊了一個 Observer,監視的事件和 CA 一樣,但優先級比 CA 要低。當 RunLoop 進入休眠前、CA 處理完事件后,ASDK 就會執行該 loop 內提交的所有任務。具體代碼見這個文件:[ASAsyncTransactionGroup](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit%2FDetails%2FTransactions%2F_ASAsyncTransactionGroup.m)。
通過這種機制,ASDK 可以在合適的機會把異步、并發的操作同步到主線程去,并且能獲得不錯的性能。
### **其他**
ASDK 中還有封裝很多高級的功能,比如滑動列表的預加載、V2.0添加的新的布局模式等。ASDK 是一個很龐大的庫,它本身并不推薦你把整個 App 全部都改為 ASDK 驅動,把最需要提升交互性能的地方用 ASDK 進行優化就足夠了。