くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

TableViewCellの再利用について

あるときこんなレビュー指摘がありました。

「CellのViewをhiddenにしているけど、ずっとhiddenになったままでいいの?再利用されるときに想定外の場合になる可能性があるよ」

最初、??と思ったのですが、そのときにTableViewCellは再利用されていることを知りました ※結構前の話です

今回はそんなセルの再利用についてまとめてみようと思います。

Cellの再利用

Cellを使う場面で真っ先に思いつくのはTableViewだと思います

TableViewのセルが大量にある場合にはViewの描画数が非常に多くなり、特にスクロール時には、パフォーマンスの低下が懸念されます

そのため、Cellの再利用で毎回新しくViewを作るのではなく以前に生成したcellを利用することで、メモリ割り当てを最小限にします

ただし、Cellの見た目を変更した際にリサイクル前のCellに対する変更が残ってしまうという問題がしばしば起こります。 例えばカスタムセルクラスの中で、データの中に画像が存在しない場合は、Cellに設置したimageViewを非表示にする処理を書いたとします。 そして、imageViewが非表示となったCellを再利用する時、画像の有無にかかわらずimageViewは非表示のままで表示されるので、注意が必要になります

Cell再利用の流れ

Cellを再利用するために、まず事前にregister(_:forCellReuseIdentifier:)を呼んで、ViewをCellのテンプレートとして登録します。 テンプレートはユニークなreuseIdentifierによって管理されます

次に、Cellの生成(参照)時、つまりfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCellメソッドが呼ばれます。 このメソッドは、再利用可能なCellがあればそれを、なければ新しく作成したCellを返します。再利用可能なCellは、reuseIdentifierに紐づけられたreuse queueに格納されています

tableviewは裏側でreuse queueという、データの箱のようなものを持っており、reuseIdentifierごとにreuse queueが存在します。 画面外に出たCellは、自身のidentifierに紐づいたreuse queueに追加されます。 そして、同じidentifierのCellが表示されようとする時、queueから取り出されます。 次の図のようにスクロールしたら次のcellがqueueから取り出されるイメージです

Cell再利用で想定外の表示にならないようにするために

ここからはCell再利用で想定外の表示にならないためにやるべきデータ初期化のタイミングについて2つ紹介します。2つともやることは同じです

まずはfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCellで行うパターンです

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellidendifier",  for: indexPath)

      // 画像を初期化
      cell.imageView.image = nil
      cell.imageView.image = UIImage(named: "test")

      return cell
}

imageViewのimageを初期化した上で画像を設定しています。この方法だとfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCellに多く初期化する処理を書く可能性があり、微妙なところです

もう1つはカスタムセルのprepareForReuse()で初期化を行うです

prepareForReuse()は再利用可能なセルを準備するときに呼ばれます。このタイミングで初期化を行います

class CustomTableViewCell: UITableViewCell {
    @IBOutlet private weak var label: UILabel!

    // セルのリサイクル対策での初期化処理を行う
    override func prepareForReuse() {
        super.prepareForReuse()
    }

    func configure(text: String) {
        label.text = text
    }
}

上記のようにするとconfigureメソッドやオートレイアウトを使う場合にはawakeFromNib()で初期化を行う必要がないです

参照

shiba1014.medium.com

developer.apple.com