見積もりの高さでUITableViewを高速化する話。

  • 253
    いいね
  • 5
    コメント
この記事は最終更新日から1年以上が経過しています。

最初に。内容に誤謬がありましたら申し訳在りません。訂正を歓迎します。

tableView:heightForRowAtIndexPath: は rowHeight で置き換えるべきか

UITableViewCellの高さが常に一定の時はrowHeightを使う - Qiita

この記事には正しいことが書いてあるのですけど、影響があるのは **表示されるセル数** が100や1000に到達するような稀有なケースです。
通常のテーブルビューでは、セルは一度に高々12程度しか表示しないため、`tableView:heightForRowAtIndexPath:`を`rowHeight`に置き換えることによる劇的なパフォーマンス良化はありません。

このことについて、rowHeightプロパティのリファレンスには次のように触れています。

There are performance implications to using tableView:heightForRowAtIndexPath: instead of rowHeight. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).

ここでは約1,000回以上`delegate`メソッドが呼び出されると、重篤なパフォーマンス悪化が起きると警告していますが、通常の利用ではまずありえない状況です。

UPDATE: コメントにてkishikawakatsumi様よりご指摘を頂きました。実際にパフォーマンスに影響を及ぼすのは、「表示されているセル数」ではなく「dataSourceの全体の数」が正しいとのことです。というのも、スクロールバーのためにcontentSizeの値を計算しなければならないため、一度delegateに対して全てのindexPathに対して高さを取得する必要があるからです。

よってdataSourcerowの数によっては置き換えによるパフォーマンス良化はある、が正しいかと思われます。申し訳在りません。

問題は動的なセルの高さの計算

UITableViewの実際的な利用時には、主としてテキスト長の問題により、セルの高さを動的に決定しなければならないケースが多いかと思います。

そしてユーザー体験を向上させる上で重要なのは、 セルの高さが動的となるdataSourceの読み込み時に、セル高の計算量で発生する負荷を軽減する ようにすること、です。

セルの高さを取得するには、intrinsicContentSizesizeWithThats:boundingRectWithSize:options:attributes:context:のような高コストのメソッドを使わざるを得ません。

もし可変長の長いタイムラインを読み込む必要性のあるアプリの場合、この呼び出しによる計算量は無視できなくなります。

※もし sizeWithFont:を未だ使っている人がいるとしたら、使用を即座にやめましょう 。このメソッドはdeprecatedになりましたし、なによりiOS7 SDK上では正しい結果を返しません。

高さの見積もりを返す

そこでiOS7からは、UITableViewDelegatetableView:estimatedHeightForRowAtIndexPath: が追加されました。

このメソッドを実装する場合、当該のindexPathのテーブルビューセルが必要とする高さを、見積もった値を返却するようにします。例えば文字数からおおよその行数を割り出すなどでしょうか。

するとUITableViewはまずその値を利用し、実際にセルを描画する必要が生じるタイミングまで、 実際の計算を遅延させることができる のです。

当然ながら、tableView:estimatedHeightForRowAtIndexPathの実装は軽量である必要があります。そして、必ずtableView:heightForRowAtIndexPath:を同時に実装する必要があります。

また、推定の高さで固定長で扱って問題がない場合、UITableViewestimatedHeightプロパティをセットすることもできます。このプロパティのデフォルト値は0で、推測の高さを持たないことを意味しています。

高さの見積もりを返さなくていいケース

高さ計算のコストが低く、見積もりを返さなくても問題ないと判断できるケースでは、UITableViewAutomaticDimensionの定数を返却することができます。

この定数を返却された場合、通常通り高さを計算します。

何気に見逃している人が多いと思うのですが、この定数はiOS5から存在するもので、tableView:heightForRowAtIndexPathでも利用することができます。この場合はrowHeightの値が適用されます。

まとめ

UITableViewが行の高さを必要としたときの流れをまとめます。

  1. delegatetableView:estimatedHeightForRowAtIndexPathに応答するかチェックします。した場合は、暫定的にその返却値を見積もりの高さとして利用します。
  2. UITableViewestimatedHeightプロパティを持つかチェックします。0以外の値がセットされている場合、暫定的に見積もりの高さとして利用します。
  3. delegatetableView:heightForRowAtIndexPathに応答するかチェックします。応答し、かつ1.と2.が実装されていない場合、UITableViewcontentSizeの計算のために即座に高さを取得します。実装されている場合、呼び出しは実際にセルを表示されるタイミングまで遅延されます。
  4. 1.および3.に応答しない場合、rowHeightプロパティを高さの値として利用します。