简体   繁体   中英

Swift protocol with associated type: how to use in an abstract method?

I have two protocols, one for a ViewModel and one for a ConfigurableView that takes the ViewModel type as an associated type.:

public protocol ViewModel {}

public protocol ConfigurableView {

  associatedtype ViewModelType: ViewModel

  func configure(with viewModel: ViewModelType)

}

In my method that configures an abstract view with an abstract model:

let viewModel = getMyViewModel() // returns ViewModel

if let configurableView = cell as? ConfigurableView {
    configurableView.configure(with: viewModel)
}

I get "Protocol 'ConfigurableView' can only be used as a generic constraint because it has Self or associated type requirements".

How do I tell the compiler that I want to configure the view with whatever associated type this instance has, if it's a ConfigurableView instance?

I actually found what I think is a decent solution that didn't require too much mangling of my architecture. Thanks to @lib for putting me on the right path. The trick was to have a protocol above that doesn't have an associatedType requirement with an extension that casts the generic ViewModel to the associatedType of the specific one. I believe this is type erasure? But it doesn't look like any of the examples I read.

public protocol ViewModel {}

/*
 This parent protocol exists so callers can call configure on
 a ConfigurableView they don't know the specific type of.
*/
public protocol AnyConfigurableView {

  func configure(with anyViewModel: ViewModel)

}

public protocol ConfigurableView: AnyConfigurableView {

  associatedtype ViewModelType: ViewModel

  func configure(with viewModel: ViewModelType)

}

/*
 This extension does the trick of converting from the generic
 form of ConfigurableView to the specific form.
 */
public extension ConfigurableView {

  func configure(with anyViewModel: ViewModel) {

    guard let viewModel = anyViewModel as? ViewModelType else {
      return
    }

    configure(with: viewModel)

  }

}

Usage:

let viewModel = getViewModel()
(someView as? AnyConfigurableView)?.configure(with: viewModel)

You cannot use Generic Protocols other way than type constraints. Without generic type defined, compiler cannot compare type conformity. If I understood you correctly, then you need to define generic CellConfigurator class. One of possible solutions below:

1. Cell and configurator abstractions

protocol ConfigurableCell {
    associatedtype DataType
    func configure(viewModel: DataType?)
}

protocol CollectionViewCellConfigurator {
    static var reuseId: String { get }
    func configure(cell: UICollectionViewCell)
    var item: UniqueIdentifiable? { get }
}

final class CellConfigurator<CellType: ConfigurableCell, DataType>: CollectionViewCellConfigurator where CellType.DataType == DataType, CellType: UICollectionViewCell {

    /// Cell Reuse identifier
    static var reuseId: String { return CellType.reuseId }

    /// Configures cell and populates it with `viewModel`
    ///
    /// - Parameter cell: Cell to configure
    func configure(cell: UICollectionViewCell) {
        (cell as! CellType).configure(viewModel: item as? DataType)
    }

    /// Initializer
    ///
    /// - Parameter item: Data item (usually ViewModel of the cell)
    init(item: DataType?) {
        self.item = item
    }
}

2. Usage

Your data source will now operate with CellConfigurators looking like CellConfigurator<CellType /*UI(CollectionView/TableView)Cell subclass*/, CellData /*Data you need to populate to the cell*/>(item: cellData)

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let configItem = yourDataSource.rows[indexPath.row]
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: configItem).reuseId, for: indexPath)
        configItem.configure(cell: cell)
        return cell
    }

Hope it helps. Good luck

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM