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の導入はアプリ毎に様々な側面から判断すべきです。