UITableViewCellの再利用を知る
UITableViewは描画されるviewが多く、その実装方法によってはアプリのパフォーマンスを著しく低下させます。本記事では、パフォーマンス対策のために導入されているcellの再利用について解説します。
UITableView
TableViewは、大量にある同じ種類の情報をリスト表示する際に利用します。そのため状況によってはviewの描画数が非常に多くなり、特にスクロール時には、パフォーマンスの低下が懸念されます。これを解決するのがcellの再利用です。毎回新しくviewを作るのではなく以前に生成したcellを利用することで、メモリ割り当てを最小限にします。
Cellの再利用
cellを再利用するために、まず事前にregister(_:forCellReuseIdentifier:)
を呼んでおきます。用意したviewをcellのテンプレートとして登録するメソッドです。テンプレートたちはユニークなreuseIdentifierによって管理されます。
次に、cellの生成時、つまりtableView(_:cellForRowAt:)
の中でdequeueReusableCell(withIdentifier:for:)
を呼びます。このメソッドは、再利用可能なcellがあればそれを、なければ新しく作成したcellを返します。再利用可能なcellは、reuseIdentifierに紐づけられたreuse queueに格納されています。
Reuse Queue
TableViewは裏側でreuse queueというものを持っており、reuseIdentifierごとにreuse queueが存在します。画面外に出たcellは、自身のidentifierに紐づいたreuse queueに追加されます。そして、同じidentifierのcellが表示されようとする時、queueから取り出されます。
Prepare for Reuse
この再利用の仕組みによって、cellの見た目を変更した際に「以前のcellに対する変更が残ってしまう」という問題がしばしば起こります。例えばカスタムセルクラスの中で、データの中に画像が存在しない場合は、cellに設置したimageViewを非表示にする処理を書いたとします。このような実装において、imageViewが非表示となったcellを再利用する時、画像の有無にかかわらずimageViewは非表示のままになってしまいます。
これを解決するのが prepareForReuse()
メソッドです。これはUITableViewCellのインスタンスメソッドで、cellが再利用される前に呼ばれます。この中にcellの見た目をデフォルトの状態に戻す処理を書きます。上の例でいうと、imageView.isHidden
をfalse
にするなどの処理です。こうすることで、再利用時に前の変更が残ってしまうことを防ぎます。また、デフォルト実装を切り分けることができるので、平易なコードを維持します。
dequeue系メソッド
再利用可能なcellを取得するメソッドは、dequeueReusableCell(withIdentifier:for:)
の他にdequeueReusableCell(withIdentifier:)
というものが存在します。こちらはiOS 5以前から使われていたものです。
まず挙げられる違いとして、古いメソッドは引数にindexPathを渡さないという点です。新しいメソッド(=dequeueReusableCell(withIdentifier:for:)
)はindexPathを渡していますが、これはtableView(_:heightForRowAt:)
になどで行われる追加の設定をチェックするために用いられます。
次に挙げられる違いは、古いメソッドは返り値がoptionalである点です。先ほどcellの生成前にはregister
メソッドを呼ぶ必要があると述べましたが、dequeueReusableCell
メソッド渡されたidentifierと一致するテンプレートが登録されていなかった場合、新しいメソッドはクラッシュしますが、古いメソッドはnilを返します。
公式ドキュメントの中では、古いメソッドはcellの取得のみの挙動に対し、新しいメソッドはcellの取得およびtableViewへの追加を行うような記述が見られます。この違いは定かではありませんが、新しいメソッドはtableView(_:cellForRowAt:)
外では呼び出さないようにと明言されています。したがって、tableView(_:cellForRowAt:)
内ではindexPathが必要となる新しいメソッドを、tableView(_:cellForRowAt:)
外でcellを生成しなければならない場合にはindexPathを必要としない古いメソッドを呼ぶと考えるのが良いです。
// Inside of tableView(_:cellForRowAt:) dequeueReusableCell(withIdentifier:for:)// Outside of tableView(_:cellForRowAt:)
dequeueReusableCell(withIdentifier:)
以上でUITableViewCellの再利用についての解説を終わります。TableViewはアプリのパフォーマンスを大きく左右する箇所なので、きちんと理解した上で実装していきたいですね。
参考文献