UICollectionView.CellRegistration and dequeueConfiguredReusableCell with nil for Item? causes runtime crash

Modern collection views use UICollectionViewDiffableDataSource with UICollectionView.CellRegistration and UICollectionView.dequeueConfiguredReusableCell(registration:indexPath:item).

There are runtime crashes when passing nil as argument for the item parameter. There's no clear documentation on whether optional items are allowed or not.

The function signature in Swift is:

@MainActor @preconcurrency func dequeueConfiguredReusableCell<Cell, Item>(using registration: UICollectionView.CellRegistration<Cell, Item>, for indexPath: IndexPath, item: Item?) -> Cell where Cell : UICollectionViewCell

Given the Item? type one would assume Optionals are allowed.

In Objective-C the signature is:

- (__kindof UICollectionViewCell *)dequeueConfiguredReusableCellWithRegistration:(UICollectionViewCellRegistration *)registration forIndexPath:(NSIndexPath *)indexPath item:(id)item;

I'm not sure, if there's implicit nullability bridging to the Swift API or if the Objective-C files has some explicit nullability annotation.

The crash is due to a swift_dynamicCast failing with:

Could not cast value of type '__SwiftNull' (0x10b1c4dd0) to 'Item' (0x10d6086e0).

It's possible to workaround this by making a custom Optional type like

enum MyOptional<T> {
    case nothing
    case something(T)
}

and then wrapping and unwrapping Item? to MyOptional<Item>. But this feels like unnecessary boilerplate.

With the current situation it's easy to ship an app where everything seems to work, but in production only certain edge cases cause nil values being used and then crashing the app.

  • Please clarify the allowed arguments / types for the dequeueConfiguredReusableCell function.
  • Either Optionals should be supported and not crash at runtime or the signatures should be changed so there's a compile time error, when trying to use an Item?.

Feedback: FB16494078

Thanks for the post and apologies for the delay.

Thanks for filing the bug, do you have a simple focused project that shows the issue clearly that can be added into the FB16494078?

This will help engineering reproduce quickly.

You can see the status of your feedback in Feedback Assistant.

Albert Pascual
  Worldwide Developer Relations.

Here’s a sample project. After more thought it makes sense that nil or Optional is dangerous anyway. As there’s the case where two models would be nil and then the ItemIdentifier is no longer unique in a snapshot, which will also crash as it’s not supported. For Optional ItemIdentifier multiple nil values should have the same hash value so this will cause the issues.

The documentation and APIs should probably be updated to mention the uniqueness requirements and that Optionals are not supported due to this.

The fact that UICollectionViewDiffableDataSource can be defined with Optionals and that dequeueConfiguredReusableCell takes Item? seems like wrong API design to me now.

class ViewController: UICollectionViewController {

  let crash: Bool = true

  @ViewLoading var dataSource: UICollectionViewDiffableDataSource<Int, Int?>
  override func viewDidLoad() {
    super.viewDidLoad()
    let registration = UICollectionView.CellRegistration<UICollectionViewListCell, Int?> { cell, indexPath, itemIdentifier in
      var config = cell.defaultContentConfiguration()
      config.text = itemIdentifier.map { String($0) } ?? "nil"
      cell.contentConfiguration = config
    }
    dataSource = UICollectionViewDiffableDataSource(collectionView: self.collectionView) { collectionView, indexPath, itemIdentifier in
      collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: itemIdentifier)
    }
    var snapshot: NSDiffableDataSourceSnapshot<Int, Int?> = .init()
    snapshot.appendSections([0])
    if crash {
      snapshot.appendItems([1,2,nil], toSection: 0)
    } else {
      snapshot.appendItems([1,2,3], toSection: 0)
    }
    dataSource.apply(snapshot, animatingDifferences: true)
  }
}

I also attached it to the feedback.

Sorry, I made a mistake with my sample. ItemIdentifiers have to be unique and therefore non optional as per my previous comment. But a nil view model mapped from the ItemIdentifier should be supported. This is the updated sample code:

class ViewController: UICollectionViewController {

  let crash: Bool = true

  @ViewLoading var dataSource: UICollectionViewDiffableDataSource<Int, Int>
  override func viewDidLoad() {
    super.viewDidLoad()
    let registration = UICollectionView.CellRegistration<UICollectionViewListCell, Int?> { cell, indexPath, itemIdentifier in
      var config = cell.defaultContentConfiguration()
      config.text = itemIdentifier.map { String($0) } ?? "nil"
      cell.contentConfiguration = config
    }
    dataSource = UICollectionViewDiffableDataSource(collectionView: self.collectionView) { [crash] collectionView, indexPath, itemIdentifier in
      let model: Int? = if crash {
        nil
      } else {
        itemIdentifier
      }
      return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: model)
    }
    var snapshot: NSDiffableDataSourceSnapshot<Int, Int> = .init()
    snapshot.appendSections([0])
    snapshot.appendItems([1,2,3], toSection: 0)
    dataSource.apply(snapshot, animatingDifferences: true)
  }

}
UICollectionView.CellRegistration and dequeueConfiguredReusableCell with nil for Item? causes runtime crash
 
 
Q