你的代码问题在于,你使用了
Model
这个术语,它并没有保证实现了
Hashable
协议。正如你所指出的那样,告诉编译器让
Model
遵循
Hashable
协议的问题是,你失去了以符合
Model
协议的异构类型为基础进行操作的能力。
如果你一开始就不关心是否符合
Model
协议,那么你可以使用标准库中的
AnyHashable
类型擦除包装器,用于完全任意的符合
Hashable
协议的实例。
然而,如果您确实关心模型符合性,那么您将不得不为既符合
Model
又符合
Hashable
的实例构建自己的
类型擦除包装器。在
我的答案中,我演示了如何为符合
Equatable
的类型构建类型擦除器。那里的逻辑可以很容易地扩展到
Hashable
——我们只需要存储一个额外的函数来返回实例的
hashValue
。
例如:
struct AnyHashableModel : Model, Hashable {
static func ==(lhs: AnyHashableModel, rhs: AnyHashableModel) -> Bool {
return lhs._isEqual(rhs) || rhs._isEqual(lhs)
}
private let base: Model
private let _isEqual: (_ to: AnyHashableModel) -> Bool
private let _hashValue: () -> Int
init<T : Model>(_ base: T) where T : Hashable {
self.base = base
_isEqual = {
if let other = $0.base as? T {
return base == other
} else {
return false
}
}
_hashValue = { base.hashValue }
}
var hashValue: Int { return _hashValue() }
}
然后您可以像这样使用它:
func complete(with models: [AnyHashableModel]) {
doSomethingWithHashable(models)
}
func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
}
let models = [AnyHashableModel(Contact()), AnyHashableModel(Address())]
complete(with: models)
我假设你也想将其用作 Model 要求的包装器(如果有的话)。或者,你可以暴露 base 属性并从 AnyHashableModel 自身中删除 Model 符合性,使调用者访问基础 Model 符合实例的 base。
struct AnyHashableModel : Hashable {
let base: Model
}
但是请注意,上述的类型擦除包装器仅适用于既实现了 Hashable
又实现了 Model
协议的类型。如果我们想要谈论一些其他遵循 Hashable
协议的实例,该怎么办呢?
更通用的解决方案(就像我在 这个问答 中展示的那样)是接受同时实现了 Hashable
和某个其他协议的类型——其类型由泛型占位符表示。
由于在 Swift 中目前没有办法表达一个泛型占位符必须符合由另一个泛型占位符给定的协议,这种关系必须由调用者通过一个 transform
闭包来定义以执行必要的向上转换。不过,由于 Swift 3.1 接受扩展中的具体同类型要求,我们可以为 Model
定义一个便利初始化程序来消除这个样板代码(并且这可以重复用于其他协议类型)。
例如:
struct AnySpecificHashable<T> : Hashable {
static func ==(lhs: AnySpecificHashable, rhs: AnySpecificHashable) -> Bool {
return lhs._isEqual(rhs) || rhs._isEqual(lhs)
}
let base: T
private let _isEqual: (_ to: AnySpecificHashable) -> Bool
private let _hashValue: () -> Int
init<U : Hashable>(_ base: U, upcast: (U) -> T) {
self.base = upcast(base)
_isEqual = {
if let other = $0.base as? U {
return base == other
} else {
return false
}
}
_hashValue = { base.hashValue }
}
var hashValue: Int { return _hashValue() }
}
extension AnySpecificHashable where T == Model {
init<U : Model>(_ base: U) where U : Hashable {
self.init(base, upcast: { $0 })
}
}
您现在需要将实例包装在一个
AnySpecificHashable<Model>
中:
func complete(with models: [AnySpecificHashable<Model>]) {
doSomethingWithHashable(models)
}
func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
}
let models: [AnySpecificHashable<Model>] = [
AnySpecificHashable(Contact()),
AnySpecificHashable(Address())
]
complete(with: models)
AnyVehicle
的方式构建一个类型擦除包装器AnyHashableModel
(主要区别只是额外存储了一个hashValue
的函数)。 - Hamish