在 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)
}
}

代码编译良好,但是当我在视图控制器上将视图模型声明为属性时,编译器失败并显示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
}
...
}

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

更新:

AddressBookCellModelType和AddressBookCellModel在这里有什么关系?

它是实现协议的结构。

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

您是否尝试过仅将其用作泛型,例如警告/错误:

class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
private var viewModel: T!
func configure(viewModel: T) {
self.viewModel = viewModel
}
...
}

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

为了扩展我在评论中的问题,查看此代码,它看起来在不添加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>()

这只是 Swift 规范,你不能使用"具有关联类型的协议"作为类型声明。原因是编译器在编译时无法知道关联的类型实际上是什么,这违反了 Swift 的"类型安全"。

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

最新更新