tvOS 15 - Cell registration inside a diffable data source cell provider exception

What follows is about tvOS 15 but may also apply to iOS 15.

Our app may display several types of UICollectionView cells and it depends on the content sent by our backend. Prior to tvOS 15, we used to lazily instantiate UICollectionView cell registrations when needed for the first time while building a UICollectionViewDiffableDataSource. Eventually, each cell registration is only instantiated once (or never).

Now tvOS 15 throws an exception when doing so, pretending to prevent the app from creating a new cell registration each time. And it does not care whether it's actually a new cell registration each time or one that is lazily instantiated once.

Here is the exception:

*** Assertion failure in -[UICollectionView dequeueConfiguredReusableCellWithRegistration:forIndexPath:item:], UICollectionView.m:7413

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempted to dequeue a cell using a registration that was created inside -collectionView:cellForItemAtIndexPath: or inside a UICollectionViewDiffableDataSource cell provider. Creating a new registration each time a cell is requested will prevent reuse and cause created cells to remain inaccessible in memory for the lifetime of the collection view. Registrations should be created up front and reused. Registration: <UICollectionViewCellRegistration: 0x6000000f8c60>'

I understand the concern but lazy instantiation is a thing and it should not be for forbidden for the sake of best practices that are not related to it. In my humble opinion, this assert should be removed and apps should be allowed to create cell registrations on the fly.

Answered by Frameworks Engineer in 689220022

Please see further details and explanation behind this change in iOS and tvOS 15 here: https://developer.apple.com/forums/thread/681739

While lazy creation of cell registrations is no longer supported, it should be very straightforward to ensure all possible cell registrations are created up front when first loading the collection view. Each cell registration is very lightweight, and you should not see performance issues even if you have a large number of registrations.

But if you have a use case that absolutely requires lazy registration of cell types, you can still achieve this using the traditional API to register cell classes or nibs using a reuse identifier, and then dequeue cells using those identifiers.

Well registrating a cell only when you need it seems like a bad practice. I don't know how many different cell types you have on the same collection view but I would suggest registrating all needed cells in viewDidLoad() method seems like when you are using the same cell again it registers again but its already registered so it crashes your app.

If you can't provide registrating all cells at once -> decompose your collection view to maybe different types of needs and depending on that need register cells you require.

If your cells registeration requires collectionView data - its bad practice

Thanks azhukov for your response.

To give a bit of context, the content displayed by our app is mostly defined by the data the app receives from our backend (including the content hierarchy). It does not know beforehand which kinds of cells it will be asked to display. Most of the screens are base on the very same UIViewController (embedding a collectionView) which is highly configurable and controlled by the server data. For now we use 8 different cells types but it regularly increases as we include more various types of content, hence ways to present it.

Our cells registrations lazy instantiation are global and are done once and for all; these instantiations are not local to a UIViewController (in the viewDidLoad() as you assumed).

Moreover, the collectionView data describes the cells to use for each section and it makes no sense to parse the data before building the data source in order to force the lazy instantiation of cells registration that will be used.

Finally, the cell registrations being only necessary in the UICollectionViewDiffableDataSource cell provider block, it seems absurd to instantiate all cell registrations beforehand whereas only some of them may be used during the app lifetime.

     // sections cells registration
    dataSource = UICollectionViewDiffableDataSource<Section, SectionItem>(collectionView: collectionView) { collectionView, indexPath, item in
      let cellRegistration = item.section.widgetConfig.tileConfig.type.cellRegistration // lazily instantiated
      return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
    }

Instantiating objects once and for all and only when required to is definitely a good practice, just as are Swift class properties. And forbidding cell registrations instantiation inside a diffableDataSource cell provider block seems to me a false good idea introduced in iOS/tvOS 15.

Accepted Answer

Please see further details and explanation behind this change in iOS and tvOS 15 here: https://developer.apple.com/forums/thread/681739

While lazy creation of cell registrations is no longer supported, it should be very straightforward to ensure all possible cell registrations are created up front when first loading the collection view. Each cell registration is very lightweight, and you should not see performance issues even if you have a large number of registrations.

But if you have a use case that absolutely requires lazy registration of cell types, you can still achieve this using the traditional API to register cell classes or nibs using a reuse identifier, and then dequeue cells using those identifiers.

tvOS 15 - Cell registration inside a diffable data source cell provider exception
 
 
Q