Xcode
iOS
Swift
Texture
asyncdisplaykit

Texture(旧AsyncDisplayKit)の仕組みを理解する

Textureは60fpsを実現するiOS用の高速非同期UIフレームワークです。Pinterestが開発しており、Pinterestアプリでも使用されています。以前はAsyncDisplayKitという名前でFacebookが開発していました。

「Async」という以前の名称からも分かるように、Textureは「非同期」であるというのが大きな特徴です。iOSのUIKitはすべてメインスレッドで操作する必要があり、それがパフォーマンスのボトルネックになる場合があります。例えばTableViewのCellを表示する場合、UIKitではひとつのスレッドがCellを上から順番に描画しています。Textureでは複数のスレッドが複数のCellを同時に描画するため、パフォーマンスが格段に上がります。

Textureの仕組み

Textureは内部的にUIKitのコンポーネントを利用しています。例えばボタンであるASButtonNodeは内部的にUIViewを使っています。またテーブルビューであるASTableNodeは内部でUITableViewを利用しています。それではTextureはどのように非同期でUIを作り上げているのでしょうか。

その答えはCALayerです。具体的に言うと、CALayer.contentsにオフスクリーン画像(UIGraphicsBeginImageContextWithOptionsで作成するUIImage)を入れることで非同期を実現しています。iOSではテキストの幅や高さを取得したり、テキストや画像を描画したりするコードはメインスレッド以外でも動作します。Textureはこれを利用して、例えばテーブルビューのセルを描画する際に、セルと同じサイズのオフスクリーン画像を作り、そこにセルのコンテンツを描き込む処理を別スレッドで行なっています。そして出来上がった画像をCALayer.contentsにセットするところだけをメインスレッドで実行することにより、メインスレッドの負荷を最小限に抑え、60fpsを実現しています。

UIが表示されるまでの流れ

ASTableNodeを例にして、Textureがどのように動いているのかを見てみます。ASTableNodeを作成してサブノード(Textureのビュー階層)に追加すると、内部的にUITableViewが作成され、画面に追加されます。そしてUITableViewのDataSourceであるtableView(_:cellForRowAt:)が呼ばれたタイミングで、UITableViewCellが作成されます。

ASTableNodeはPreloadingに対応しており、UITableViewCellが作成される以前からデータが準備されます。この時、ASTableNodeのDataSourceであるtableNode(_:nodeBlockForRowAt:)が呼ばれます。ASTableDataSourceではセルを返すのではなく、セルを描画するASCellNodeクラスを返します。

ASCellNodeは内部的にUIViewを保持しており、非同期でUIを表示します。例えばASTextCellNodeはTextKitを利用してUIImageにテキストを描画し、それをCALayer.contentsにセットします。このビューがUITableViewCellに追加されてUITableViewにコンテンツが表示されます。

Textureの他のUIコンポーネントも内部でUIKitのビューを持ち、UIImageに描画してCALayer.contentsに入れるような仕組みでUIを表示しています。

Textureのデメリット

このようにいいことずくめに見えるTextureですが、デメリットもあります。それはメモリとバッテリーを多く消費するということです。UIKitと比べて、画像を作成して描画する分だけメモリとバッテリーを消費します。そしてASTableNodeやコレクションビューのASCollectionNodeの場合は、セルが画面に表示されるタイミングで毎回画像の作成と描画が発生するため、その影響は小さくないと考えます。

またTextureの導入には大きなコードの変更が必要となります。UIKitのViewはTextureのNodeに、そしてViewControllerはASViewControllerに置き換える必要があります。またTextureではAuto Layoutも使えないため、レイアウトコードも書き直す必要があります。

Textureの導入はアプリ毎に様々な側面から判断すべきです。