具有关联类型的Swift协议:如何在抽象方法中使用?

时间:2018-10-21 19:26:21

标签: swift protocols associated-types

我有两种协议,一种用于ViewModel,一种用于将ViewModel类型作为关联类型的ConfigurableView。

public protocol ViewModel {}

public protocol ConfigurableView {

  associatedtype ViewModelType: ViewModel

  func configure(with viewModel: ViewModelType)

}

在我的使用抽象模型配置抽象视图的方法中:

let viewModel = getMyViewModel() // returns ViewModel

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

我得到“协议'ConfigurableView'只能用作通用约束,因为它具有Self或关联的类型要求”。

如果它是ConfigurableView实例,如何告诉编译器我要使用该实例具有的任何关联类型来配置视图?

2 个答案:

答案 0 :(得分:1)

实际上,我发现我认为是一个不错的解决方案,不需要过多地修改我的体系结构。感谢@lib使我走上正确的道路。诀窍是要在上面的协议中没有对associatedType的要求,并且要有一个将通用ViewModel强制转换为特定类型的associatedType的扩展。相信这是类型擦除吗?但这看起来与我阅读的任何示例都不一样。

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)

  }

}

用法:

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

答案 1 :(得分:0)

除了类型约束,您不能以其他方式使用通用协议。如果没有定义泛型类型,编译器将无法比较类型一致性。如果我对您的理解正确,则需要定义通用的CellConfigurator类。以下是可能的解决方案之一:

1。单元和配置器抽象

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。用法

您的数据源现在将以CellConfigurators的形式运行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
    }

希望有帮助。祝你好运