使用具有约束关联类型的协议作为 属性 in Swift

Use protocol with constrained associated type as property in Swift

我正在尝试实现具有关联类型的数据源协议

protocol DataSourceCompatible {
    associatedtype CellModel
    func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

协议AddressBookViewModelType继承自基本协议并将关联值约束到另一个协议

protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}

AddressBookViewModelAddressBookViewModelType协议的具体实现

class AddressBookViewModel: AddressBookViewModelType {
    func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
        let contact = sectionedContacts[indexPath.section][indexPath.row]
        return AddressBookCellModel(contact: contact)
    }
}

代码编译正常,但是当我在 viewcontroller 上将视图模型声明为 属性 时,编译器失败并显示 Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements

class AddressBookViewController: UIViewController {
    private var viewModel: AddressBookViewModelType!

    func configure(viewModel: AddressBookViewModelType) {
        self.viewModel = viewModel
    }
...
}

我记得看到类型擦除可能会解决问题,但我对类型擦除的概念不是很熟悉。有办法解决这个问题吗?

更新:

How are AddressBookCellModelType and AddressBookCellModel related here?

它是一个实现协议的结构体。

protocol AddressBookCellModelType {
    var name: String { get }
    var photo: UIImage? { get }
    var isInvited: Bool { get }
}

struct AddressBookCellModel: AddressBookCellModelType {
 ....
}

你有没有试过把它当作一个通用的,就像warning/error说的:

class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
    private var viewModel: T!

    func configure(viewModel: T) {
        self.viewModel = viewModel
    }
    ...
}

您需要使用 属性 变量 T 初始化您的控制器,以便推断类型。

这只是 Swift 规范,您不能使用 'protocol with associated type' 作为类型声明。原因是编译器无法在编译时知道关联的类型实际上是什么,这违反了Swift的"type-safety"。

解决方案是像你说的那样使用类型橡皮擦,或者使类型通用。

为了在评论中扩展我的问题,看看这段代码,它看起来就像不添加 AddressBookCellModelTypeAddressBookViewModelType 一样灵活,而且这也可以消除令人头疼的问题,同时在 DataSourceCompatible.

上仍然是通用的
// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
    associatedtype CellModel
    func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)    
struct AddressBookCellModel {
    var name: String
    var photo: UIImage?
    var isInvited: Bool
}

// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
    let sectionedContacts: [[AddressBookCellModel]] = []
    func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
        return sectionedContacts[indexPath.section][indexPath.row]
    }
}

class AddressBookViewController: UIViewController {
    private var viewModel: AddressBookViewModel!

    func configure(viewModel: AddressBookViewModel) {
        self.viewModel = viewModel
    }
}

这样做可以实现通用 VC,而无需引入更多需要的部分:

class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
    private var viewModel: DataSource.CellModel!

    func configure(viewModel: DataSource.CellModel) {
        self.viewModel = viewModel
    }
}

let vc = DataSourceViewController<AddressBookViewModel>()