Generics with diffable data source for multiple sections / cell types

Hi guys.

I am trying to implement collection view screen with multiple sections and cell types using diffable data source. I define it using Sections enum and SectionItem struct which keeps nested model for cells configuration

private var dataSource: UICollectionViewDiffableDataSource<Behavior.Section, SectionItem>?

public typealias HashableAndSendable = Hashable & Sendable

public protocol ItemProtocol: HashableAndSendable {
  var model: any HashableAndSendable { get }
}

public struct SectionItem: ItemProtocol {

  public var model: any HashableAndSendable

  public func hash(into hasher: inout Hasher) {
    hasher.combine(model)
  }

  public static func == (lhs: SectionItem, rhs: SectionItem) -> Bool {
    lhs.model.hashValue == rhs.model.hashValue
  }
}

I want to have cells with configure function with its own model struct as a param

public protocol ConfigurableProtocol {
  associatedtype Model: HashableAndSendable
  @MainActor
  func configure(with model: Model)
}

public typealias ConfigurableCell = UICollectionViewCell & ConfigurableProtocol

In my View Controller I am setting up my cells using UICollectionView.CellRegistration handler

var cellsRegistrations = [String: Any]()

  func registerCell<Cell>(
    cellType: Cell.Type,
    handler: @escaping UICollectionView.CellRegistration<Cell, SectionItem>.Handler
  )
  where Cell: ConfigurableCell {
    cellsRegistrations[String(describing: cell.self)] = UICollectionView.CellRegistration<Cell, SectionItem>(handler: handler)
  }

func setupCells() {
    for type in behavior.cellsTypes() {
      registerCell(cellType: type) { [unowned self] cell, indexPath, itemIdentifier in
        configure(cell: cell, model: itemIdentifier.model)
      }
    }
  }

but when trying to call configure functions I have no way of casting to proper model type of a cell

func configure(
    cell: some ConfigurableCell,
    model: some HashableAndSendable
  ) {
    // error here: 'some' types are only permitted in properties, subscripts, and functions
    cell.configure(with: model as? (some ConfigurableCell).Model)   
 }

Can I define these relationships like that or not ? What can you recommend

Thanks a lot

I'd do it simpler, e.g.:

    @MainActor func configure(cell: UICollectionViewCell, model: any Model) {
        cell.configure(with: model)
    }

Note that this fragment is problematic:

public struct SectionItem: ItemProtocol {

  public var model: any HashableAndSendable

  public func hash(into hasher: inout Hasher) {
    hasher.combine(model)
  }

  public static func == (lhs: SectionItem, rhs: SectionItem) -> Bool {
    lhs.model.hashValue == rhs.model.hashValue
  }
}
  1. you are comparing hashValues, this is suspicious (hashValues could be equal while the items themselves are not).
  2. if you are comparing hashValues (which is wrong per (1) but still) – at least use that in your hash implementation. hasher.combine(model) is not the same as hasher.combine(model.hashValue)

This smells "speculative generality", and in cases like this I'd recommend to step back and "de-generalise" the code – make it as concrete as possible initially. Once it's working, and you feel making it more generic would be beneficial (e.g. because it will half the code size) - slowly introduce generalisations back, one step at a time.

4 Likes

Yes, you're right I could implement Hashable and Equatable better and I will.

When it comes to the configure function I don't like it accepting any model and to have to cast to a proper struct it uses for configuration. This is a bad design in my opinion. Doesn't swift support better implementation? I would be disappointed