Swift 协议的一个强大之处,在于它可以作为类型约束;另一个强大之处,是 associated type,让协议可以实现一定程度的范型。
但这两个优势是互相矛盾的,如果协议内部有 associated type(或者协议引用了 Self
类型,因为这样其实也是一种 associated type 行为),这个协议就不能用于类型约束了。
下例会报错:Protocol 'Vehicle' can only be used as a generic constraint because it has Self or associated type requirements
protocol Vehicle {
associatedtype FuelType: Fuel
}
func getVehicle() -> Vehicle {
someone.vehicle
}
于是 Swift 引入了 some
关键词,或者说引入了 Opaque Type(不透明类型),总之此处加上 some
就不再报错了:
protocol Vehicle {
associatedtype FuelType: Fuel
}
func getVehicle() -> some Vehicle {
someone.vehicle
}
Opaque Type 只能作为变量类型或者函数返回值类型来使用,其他场景,比如作为函数的参数,就行不通,比如下例无论有没有 some
都无法通过编译:
func drive(_ vehicle: /* some */ Vehicle) {
// ...
}
因为这里应该用 Generic(范型),而不是 Opaque Type:
func drive<V: Vehicle>(_ vehicle: V) {
// ...
}
Generic 同样也可以解决方法返回值不能用类型约束的问题:
func drive<V: Vehicle>(_ vehicle: V) -> V {
// ...
// return ...
}
那么 Generic 和 Opaque Type 是否在职能上重叠了呢?
Opaque Type 与 Generic 的关系 / 区别
之前写过一篇相关的文章:
孙一萌:Protocol Oriented: Swift 协议陷阱之 Associated Typezhuanlan.zhihu.com今天首先贴出 关于 Opaque Types 的官方文档。
文档阐述了 Opaque Type 的设计初衷,以及它与 Generic 如何相辅相成。
套用到本例中,Opaque Type 和 Generic 都解决了协议无法作为类型约束的问题,但它们解决问题的方式是截然相反的,或者说它们的思想是有区别的。
Generic 让调用者决定参数或者返回值的类型。比如上面的 drive(_:)
方法,定义的时候只规定了参数必须遵从 Vehicle
,并没有具体安排是哪种类型。具体是什么类型,完全由传入的变量决定:
func drive<V: Vehicle>(_ vehicle: V) {
// ...
}
func example() {
let car = GasolineCar()
drive(car)
}
而 Opaque Type 是让方法本身决定返回的类型。比如 getVehicle() -> some Vehicle
,调用者完全不知道方法会返回什么类型的值,只知道它一定遵从 Vehicle
:
func getVehicle() -> some Vehicle {
someone.vehicle
}
func example() {
let car = getVehicle()
// ...
}
我们可以把它们结合起来用,让协议更强大,功能更加完整:
func getVehicle() -> some Vehicle {
someone.vehicle
}
func drive<V: Vehicle>(_ vehicle: V) {
// ...
}
func example() {
let car = getVehicle() // 1
drive(car) // 2
}