您的代码的问题在于您正在谈论Model
,它承诺nothing about Hashable
一致性。正如您所指出的,告诉编译器这一点的问题(即派生Model
from Hashable
)那么你是否就失去了用符合的异质类型进行交谈的能力Model
.
如果你根本不关心Model
首先要保持一致性,您可以使用标准库的AnyHashable类型擦除包装完全任意 Hashable
符合实例。
但是,假设您确实关心Model
一致性,你必须建立自己的类型擦除包装对于同时符合两者的实例Model
and Hashable
. In 我的回答在这里,我演示了如何构建类型橡皮擦Equatable
符合类型。那里的逻辑可以很容易地扩展为Hashable
– 我们只需要存储一个额外的函数来返回hashValue
实例的。
例如:
struct AnyHashableModel : Model, Hashable {
static func ==(lhs: AnyHashableModel, rhs: AnyHashableModel) -> Bool {
// forward to both lhs's and rhs's _isEqual in order to determine equality.
// the reason that both must be called is to preserve symmetry for when a
// superclass is being compared with a subclass.
// if you know you're always working with value types, you can omit one of them.
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 = {
// attempt to cast the passed instance to the concrete type that
// AnyHashableModel was initialised with, returning the result of that
// type's == implementation, or false otherwise.
if let other = $0.base as? T {
return base == other
} else {
return false
}
}
// simply assign a closure that captures base and returns its hashValue
_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
属性并删除Model
符合来自AnyHashableModel
本身,使调用者访问base
对于底层的Model
符合实例:
struct AnyHashableModel : Hashable {
// ...
let base: Model
// ...
}
然而,您会注意到,上面的类型擦除包装器仅适用于同时具有以下类型的类型:Hashable
and a Model
。如果我们想讨论符合实例的其他协议怎么办Hashable
?
正如我所演示的,一个更通用的解决方案在本次问答中,是改为接受两者兼而有之的类型Hashable
并符合其他一些协议——其类型由通用占位符表示。
由于目前 Swift 中无法表达必须符合另一个通用占位符给出的协议的通用占位符;这种关系必须由调用者定义transform
关闭以执行必要的向上转换。然而,由于 Swift 3.1 在扩展中接受了具体的同类型要求,我们可以定义一个方便的初始化程序来删除这个样板文件Model
(对于其他协议类型可以重复此操作)。
例如:
/// Type-erased wrapper for a type that conforms to Hashable,
/// but inherits from/conforms to a type T that doesn't necessarily require
/// Hashable conformance. In almost all cases, T should be a protocol type.
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 for convenience initialiser for when T is Model.
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)