AsyncDisplayKit技術分析

轉載請註明出處:http://xujim.github.io/ios/2014/12/07/AsyncDisplayKit_inside.html ,謝謝 

html

前言

Facebook前段時間發佈了其iOS UI框架AsyncDisplayKit(ASDK)的1.0正式版,這個框架被用於Facebook自家的應用Paper,可以提升UI的流暢性並縮短響應時間。AsyncDisplayKit附帶了guide文檔,有興趣的同窗能夠參考這裏ios

然而本文主要着重於討論AsyncDisplayKit的技術原理與其相對於於UIKit響應優化的技巧。git

衆所周知,在現行的UI framework如UIKit,Windows的DotNet一旦涉及到繪製,老是建議在UI thread中進行,而且許多API也老是依賴於UI線程,不然容易致使crash。這和各framework開發者想使本身的Framework易於使用,不易出錯,更好維護的初衷有關。 但其實UI的展示涉及的幾個主要步驟和UI線程並不是必定要綁定在一塊兒的。AsyncDisplayKit並無使用特別高深的繪製或者如GPU優化等優化技巧,而是在UI展示過程當中將幾個重要階段剝離主線程從而將UI的流暢性提升到極致。這幾個重要階段分別是佈局、繪製、圖像。下文將按這幾個階段闡述AsyncDisplayKit的技術技巧。github

佈局

UI框架中要佈局一個UIView通常須要改寫layoutSubviews或者layoutSublayers等函數,而且一旦修改了frame等屬性必然會觸發這類layout函數。而這些函數的實現每每自上而下,要根據container來measure子view的大小而且層層遞歸計算下去。若是UIView是個複雜的容器如UITableView等,則如此遞歸計算將很耗時間。 這個計算方法不可避免,但UIkit和其餘傳統的UI庫都將這些工做放入主線程。那麼一旦layoutSubview的計算成本過大,必然會致使UI的響應緩慢或者刷新有delay。編程

那就放入工做線程唄,但遇到繪製的線程同步問題,又讓許多人望而卻步。不過AsyncDisplayKit作到了。數組

以AsyncDisplayKit的ASTableView爲例,TableView的UI佈局須要計算每行的高度,而後計算行內元素的佈局,將行插入到TableView中,同時TableView又是scrollview,須要上下滑動。一旦行的生成和渲染比較慢,必然影響到滑動時的流暢體驗。在這個過程當中只有將行插入到TableView中須要在UI線程中執行。緩存

AsyncDisplayKit在子線程中分批次計算行(row)以及其子元素的佈局,計算好後經過dispatch_group_notify通知UI線程將row插入到view中。 AsyncDisplayKit有一個比較細膩的方式是考慮到設備的CPU核數,根據核數來分批次計算row的大小。網絡

每當row被sized以後,TableView便會觸發row UI實體cell的生成,隨之即是row中內容的繪製——這在後面會詳述其中的高效技巧。框架

此處的技巧具體可參見sizeNextBlock函數。異步

繪製

AsyncDisplayKit另外一個強大之處在於將UI CALayer的backing image的繪製放入到工做線程。

咱們知道UIView的真正展示內容在於其CALayer的contents屬性,而contents屬性對應一個Backing image,咱們能夠將其理解成一個內存位圖。默認狀況下UIView一旦須要展示,其會自動建立一個Backing image,但咱們也能夠經過CALayer的delegate方式來定製這個Backing image。

AsyncDisplayKit就是經過CALayer的delegate控制backing image的生成,而且經過Core Graphic的方式在工做線程上將View以及其子節點繪製到backing image上,這些繪製工做會根據UIView的層次構建一個繪製數組統一執行,繪製好以後在UI線程上將這個backing image傳給CALayer的contents,最後將CALayer的渲染樹交給GPU進行渲染。雖然這個過程當中主要依賴於CoreGraphic來進行繪製,但由於都在後臺,並且繪製以組的方式執行減小了graphic context的切換,對於UI性能和順滑性沒有什麼影響。

那backing image繪製好以後也是經過dispatch_group的方式通知UI線程嗎?若是繪製節點不少經過這個API必然會致使錯亂。AsyncDisplayKit在這裏又將iOS的異步用到極致——他經過transaction的方式管理dispatch_group之間的關係,而且只有在UI線程處於idle狀態或將退出時纔將transaction commit並將backing image賦給CALayer的contents。

除了經過CAlayer的backing image繪製,AsyncDisplayKit還提供UIView的drawRect繪製以及UIView的rasterize。二者都會使用offscreen drawing,但後者會將UIView以及全部子節點都繪製在一個backing image上

此處所使用的技巧可參見_ASAsyncTransaction類。

圖像

目前網絡上流行的圖片格式基本都是壓縮格式,而圖片的顯示大體可分爲如下幾部分:

  1. 圖像的IO加載
  2. 圖像的解壓
  3. 圖像的處理,如blend,crop等等
  4. 圖像的渲染

AsyncDisplayKit在此主要優化第二和第三階段,畢竟IO加載每每經過異步IO或者預加載的方式進行優化,圖像的渲染通常都是GPU進行快速渲染。

首先說圖像的解壓。或許你會問,咱們一般IO加載圖像後就生成UIImage了,儘管咱們知道圖像確定要解壓,但彷佛沒有API供咱們調用啊?這就是AsyncDisplayKit的高明之處。

通常UIImage對其內部圖像格式的解壓發生在即將將圖片交給GPU渲染的時候。從API上來看,通常咱們調用[UIImage drawInRect]函數或者將UIView可見(放置於window上)的時候,UIImage會將其內部圖像格式如PNG,JPEG進行解壓。AsyncDisplayKit就是採用後面那個方式將UIView預先放置入一個frame爲空得workingview中以達到將view內部的image進行預解壓的目的。

此處仍是以ASTableView爲例。當前table view中可見的rows中得圖片確定是會發生解壓的,但table view須要常常滑動rows操做,那麼可見的rows上下須要增長一些緩存區來預處理即將展現的rows,如此在互動窗口上移或下移的時候,這些緩存的rows能快速渲染並立刻展現到UI上。AsyncDisplayKit經過working range來管理這上下緩存,經過將working rows放置入frame爲(0,0,0,0)的UIWindow中進行row內部image的預解壓和預生成。

再說圖像的處理。通常圖像須要一些blend運算或者圖像須要strech或crop,這些工做其實能夠留在GPU中進行快速計算,但由於UIKit並無提供此類的API,因此咱們通常都是經過CoreGraphic來作,但CoreGraphic是CPU drawing比較費時。AsyncDisplayKit將這些圖像處理放在工做線程中來處理,雖然也是CPU drawing,但不會影響到UI得順滑響應。具體此處的技術實現能夠看ASImageNode的代碼。

結尾

綜上,AsyncDisplayKit如庖丁解牛通常熟悉UI繪製整個過程當中得經絡,將一些能夠移到工做線程的工做剝離主線程,而且高超的使用iOS中得線程技巧作好同步,達到了提供UI流暢順滑的效果,讓人心中一亮,爲之側目!

更重要的是:這些技術技巧實際上是通用的,徹底能夠用於iOS甚至Android等其餘客戶端的編程當中。

固然,AsyncDisplayKit大量的採用線程,也帶來了一些接口API在線程同步中很差使用的問題,爲了不或者解決這些問題,須要你對其原理有理解。同時AsyncDisplayKit在text繪製上採用TextKit方式,因此對老版本的iOS不兼容。

此外,本文目的在於介紹AsyncDisplayKit的一些通用技巧,因此文中沒有插入特殊的Objective c代碼片斷。並且由於時間關係,或許漏掉了AsyncDisplayKit中其餘巧妙的技巧,在此請讀者海涵:).

相關文章
相關標籤/搜索