Equatable and Hashable, be referenced directly?
如果你已经做了一段时间的Swift开发,你很有可能会遇到下面的编译器错误:
Protocol 'Equatable' can only be used as a generic constraint because it has
Self or associated type requirements.
当我们试图直接引用一个泛型协议时就会发生这种情况——也就是说,一个协议要么有关联的类型,要么要求一致性类型(指的是Self)是已知的。例如,内置的Equatable协议在其声明中使用Self,就像这样:
protocol Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool
}
这是因为相等检查只能在两个已知是完全相同类型的值之间执行-所以类似下面的函数不能工作,因为传递给它的两个参数可以是符合Equatable的任何类型:
func valueDidChange(from old: Equatable, to new: Equatable) {
guard old != new else {
return
}
...
}
另一方面,下面的方法是有效的——因为我们现在使用Equatable作为泛型类型T的约束,这给了我们一个编译时保证,两个参数将是完全相同的类型:
func valueDidChange<T: Equatable>(from old: T, to new: T) {
guard old != new else {
return
}
...
}
因此,当在协议中使用Self时,编译器需要确切地知道我们在每个给定上下文中引用的类型。为了实现这一点,我们可以直接使用符合标准的具体类型(例如使用Int或String代替Equatable),或者使用协议作为通用约束(如上所述)。
对于有关联类型的协议也是如此,因为就像Self一样,被引用的实际类型不会被知道,除非我们提供一些额外的上下文。 例如,这里我们定义了一个Loadable协议,它允许我们通过load()方法加载关联的Target类型:
protocol Loadable {
associatedtype Target
func load() -> Target
}
Target引用的确切类型取决于我们调用load()的符合类型,所以像下面这样的东西不会起作用——因为编译器不能知道加载的是什么类型:
func loadTarget(from loadable: Loadable) {
let target = loadable.load()
...
}
就像以前一样,我们可以通过使用泛型类型约束来解决上面的问题,让编译器知道每个调用站点正在使用的确切的Loadable实现:
func loadTarget<T: Loadable>(from loadable: T) {
let target = loadable.load()
...
}
现在,无论何时我们调用上述loadTarget函数,编译器将能够根据传递到函数中的Loadable类型推断T引用的类型。例如,这里编译器可以推断T表示StringLoader,而正在加载的Target是一个String值:
struct StringLoader: Loadable {
func load() -> String {
...
}
}
let loader = StringLoader()
loadTarget(from: loader)
在处理泛型协议时,还有许多其他技术非常有用——包括类型擦除、使用更高级的约束等等。请随意探索泛型和协议的类别页面以了解更多内容。