Протокол Swift со связанным типом: как использовать в абстрактном методе?

У меня есть два протокола: один для ViewModel и один для ConfigurableView, который принимает тип ViewModel в качестве связанного типа.:

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 »может использоваться только как общее ограничение, поскольку он имеет требования к собственному или связанному типу».

Как мне сообщить компилятору, что я хочу настроить представление с любым типом, связанным с этим экземпляром, если это экземпляр ConfigurableView?


person Nick Locking    schedule 21.10.2018    source источник


Ответы (2)


На самом деле я нашел то, что я считаю достойным решением, которое не требовало слишком большого искажения моей архитектуры. Спасибо @lib за то, что поставил меня на правильный путь. Хитрость заключалась в том, чтобы иметь приведенный выше протокол, который не имеет требования AssociateType с расширением, которое приводит общую ViewModel к ассоциированному типу конкретной. Я полагаю, что это стирание типа? Но это не похоже ни на один из примеров, которые я читал.

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)
person Nick Locking    schedule 22.10.2018

Вы не можете использовать универсальные протоколы иначе, чем ограничения типа. Без определения универсального типа компилятор не может сравнить соответствие типов. Если я вас правильно понял, вам нужно определить общий класс 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
    }

Надеюсь, поможет. Удачи

person fewlinesofcode    schedule 21.10.2018