ぜのぜ

しりとりしようぜのぜのぜのぜ

dequeueReusableCellの返り値をキャストできなかったときはどうするのがいいか調べたことと感想

tl;dr

1. dequeueReusableCellの返り値をそのまま返す

更新できていないセルが表示されるのを許容できるならこの案が一番穏便だと思った.

2. nilを返す

仕様的にキャストが失敗することはなさそうなので潔く落とすのもよさそう.

前提

状況

let dataSource = UICollectionViewDiffableDataSource<Int, UUID>(collectionView: collectionView) { (_, indexPath, _) in
  let cell = dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
  let customCell = cell as? CustomCell else {
    // ここでどうするか
  }

  // do something with customCell

  return customCell
}

dequeueReusableCell(withReuseIdentifier:for:)の仕様

引用元: dequeueReusableCell(withReuseIdentifier:for:)

スキーマは以下のようになっていて,

func dequeueReusableCell(withReuseIdentifier identifier: String, for indexPath: IndexPath) -> UICollectionViewCell

Return Valueの項にもこう書いてあるので,dequeueReusableCellからは有効なUICollectionViewCellが返ってきそう.

A valid UICollectionReusableView object.

ちなみに,返ってくるのは以前使ったセルか,それがなければ新しく作ったセル.

This method dequeues an existing cell if one is available or creates a new one based on the class or nib file you previously registered.

UICollectionViewDiffableDataSource.CellProviderの仕様

引用元: UICollectionViewDiffableDataSource.CellProvider

定義は以下のようになっていて,

typealias UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider = (UICollectionView, IndexPath, ItemIdentifierType) -> UICollectionViewCell?

返り値の型がUICollectionViewCell?なのでnilを返しても良さそうだが,Return Valueの項には以下のように書かれているのでnilは返せなさそう.

A non-nil configured cell object. The cell provider must return a valid cell object to the collection view.

挙動の確認

1. dequeueReusableCellの返り値をそのまま返す

先程のコードを使うと以下のようになる.

let dataSource = UICollectionViewDiffableDataSource<Int, UUID>(collectionView: collectionView) { (_, indexPath, _) in
  let cell = dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
  let customCell = cell as? CustomCell else {
    return cell // そのまま返す
  }

  // do something with customCell

  return customCell
}

dequeueReusableCellが返してくるのは以前使ったセルか,それがなければ新しく作ったセルなので更新されない状態のセルが表示されることになる.それ以外に問題はない.

2. nilを返す

コードはこんな感じ.

let dataSource = UICollectionViewDiffableDataSource<Int, UUID>(collectionView: collectionView) { (_, indexPath, _) in
  let cell = dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
  let customCell = cell as? CustomCell else {
    return nil // nilを返す
  }

  // do something with customCell

  return customCell
}

UICollectionViewDiffableDataSource.CellProviderの定義どおり問題なくコンパイルは通るが,Return Valueの項にあったとおりnon-nilなセルを返さないといけないらしく,以下のような例外を吐いて死ぬ.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection view's data source did not return a valid cell from -collectionView:cellForItemAtIndexPath: for index path <NSIndexPath: 0x8d1687fd52393dda> {length = 2, path = 0 - 1}'

3. UICollectionViewCell()を返す

例によって以下のようになる.もちろん返すのはCustomCell()でもいい.

let dataSource = UICollectionViewDiffableDataSource<Int, UUID>(collectionView: collectionView) { (_, indexPath, _) in
  let cell = dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
  let customCell = cell as? CustomCell else {
    return UICollectionViewCell() // 新しくUICollectionViewCellを作って返す
  }

  // do something with customCell

  return customCell
}

これもコンパイルは通るが以下のような例外を吐いて死ぬ.慈悲は無い.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:'

感想(結論ではない

1. dequeueReusableCellの返り値をそのまま返す

UICollectionViewDiffableDataSource.CellProviderでやりたいのは大体セルの更新だと思うので,更新できてないセルが表示されるのを許容できるならこの案が一番穏便だと思った.

2. nilを返す

dequeueReusableCellの仕様的にカスタムセルへのキャストが失敗することはなさそうなので,潔く落とすのもよさそう.

3. UICollectionViewCell()を返す

わざわざインスタンスを作って落とすならnilでいいじゃんという気がするのでこれはなし.