理解Swift 5.7中的“some”和“any”关键字 
some
和 any
关键字在Swift中并不新鲜。some
关键字是在Swift 5.1中引入的,而any
关键字是在Swift 5.6中引入的。在Swift 5.7中,苹果对这两个关键字都进行了又一次重大改进。我们现在可以在函数的参数位置使用这两个关键字!
func doSomething(with a: some MyProtocol) {
// Do something
}
func doSomething(with a: any MyProtocol) {
// Do something
}
这一改进不仅使范型函数看起来更干净,而且还解锁了一些在Swift中编写范型代码的令人兴奋的新方法。剧透-我们现在可以告别以下错误消息:
protocol can only be used as a generic constraint because it has Self or associated type requirements
想知道更多吗?请继续阅读!
准备工作
在进入细节之前,我们先定义在本文中将使用的协议。
protocol Vehicle {
var name: String { get }
associatedtype FuelType
func fillGasTank(with fuel: FuelType)
}
之后,我们将定义符合Vehicle
协议的汽车Car
和公共汽车Bus
结构, 其中每种都需要不同种类的燃料。
struct Car: Vehicle {
let name = ”car“
func fillGasTank(with fuel: Gasoline) {
print(”Fill \(name) with \(fuel.name)“)
}
}
struct Bus: Vehicle {
let name = ”bus“
func fillGasTank(with fuel: Diesel) {
print(”Fill \(name) with \(fuel.name)“)
}
}
struct Gasoline {
let name = ”gasoline“
}
struct Diesel {
let name = ”diesel“
}
请注意,fillGasTank(with:)
函数的Car
和Bus
的参数数据类型不同,汽车需要汽油(Gasoline
),而公共汽车需要柴油(Diesel
)。这就是为什么我们需要在我们的车辆协议中定义一个名为FuelType
的相关类型。
理解“some”关键词
some
关键字是在Swift 5.1中引入的。它与协议一起使用,以创建一个不透明类型,该类型表示符合特定协议的东西。当用于函数的参数位置时,这意味着函数正在接受一些符合特定协议的具体类型。
在这个阶段,你可能想知道,我们不是已经能做到了吗?
事实上,你是对的。在函数的参数位置使用some
关键字与在函数签名处使用角括号(< >
)或尾随的where
子句完全相同。
// 以下3个函数签名是相同的。
func wash<T: Vehicle>(_ vehicle: T) {
// Wash the given vehicle
}
func wash<T>(_ vehicle: T) where T: Vehicle {
// Wash the given vehicle
}
func wash(_ vehicle: some Vehicle) {
// Wash the given vehicle
}
当我们在变量上使用some
关键字时,我们告诉编译器我们正在处理一个特定的具体类型,因此不透明类型的底层类型必须针对变量的范围进行固定。
var myCar: some Vehicle = Car()
myCar = Bus() // 🔴 Compile error: Cannot assign value of type ’Bus‘ to type ’some Vehicle‘
需要注意的一点是,编译器也禁止将相同具体类型的新实例分配给变量。
var myCar: some Vehicle = Car()
myCar = Car() // 🔴 Compile error: Cannot assign value of type ’Car‘ to type ’some Vehicle‘
var myCar1: some Vehicle = Car()
var myCar2: some Vehicle = Car()
myCar2 = myCar1 // 🔴 Compile error: Cannot assign value of type ’some Vehicle‘ (type of ’myCar1‘) to type ’some Vehicle‘ (type of ’myCar2‘)
考虑到这一点,在将其与数组一起使用时,我们必须遵循相同的规则。
// ✅ No compile error
let vehicles: [some Vehicle] = [
Car(),
Car(),
Car(),
]
// 🔴 Compile error: Cannot convert value of type ’Bus‘ to expected element type ’Car‘
let vehicles: [some Vehicle] = [
Car(),
Car(),
Bus(),
]
函数的底层返回类型也是如此。
// ✅ No compile error
func createSomeVehicle() -> some Vehicle {
return Car()
}
// 🔴 Compile error: Function declares an opaque return type ‘some Vehicle’, but the return statements in its body do not have matching underlying types
func
createSomeVehicle(isPublicTransport: Bool) -> some Vehicle {
if isPublicTransport {
return Bus()
} else {
return Car()
}
}
仅此而已。现在让我们探讨any
关键字,看看它们之间有什么区别。
理解 “Any” 关键词
any
关键字是在Swift 5.6中引入的。引入它是为了创建一个存在的类型。在Swift 5.6中,在创建存在的类型时,any
关键字不是强制性的,但在Swift 5.7中,如果您没有这样做,您将收到编译错误。
let myCar: Vehicle = Car() // 🔴 Compile error in Swift 5.7: Use of protocol ’Vehicle‘ as a type must be written ’any Vehicle‘
let myCar: any Vehicle = Car() // ✅ No compile error in Swift 5.7
// 🔴 Compile error in Swift 5.7: Use of protocol ’Vehicle‘ as a type must be written ’any Vehicle‘
func wash(_ vehicle: Vehicle) {
// Wash the given vehicle
}
// ✅ No compile error in Swift 5.7
func wash(_ vehicle: any Vehicle) {
// Wash the given vehicle
}
正如苹果工程师所解释的那样,存在类型就像一个包含符合特定协议的东西的盒子。
如上图所示,不透明类型和存在类型之间的主要区别是“盒子”。 “盒子”使我们能够在其中存储任何具体类型,只要底层类型符合指定的协议,从而允许我们做不透明类型不允许我们做的事情。
// ✅ No compile error when changing the underlying data type
var myCar: any Vehicle = Car()
myCar = Bus()
myCar = Car()
// ✅ No compile error when returning different kind of concrete type
func createAnyVehicle(isPublicTransport: Bool) -> any Vehicle {
if isPublicTransport {
return Bus()
} else {
return Car()
}
}
最棒的是,在Swift 5.7中,我们现在可以将any
关键字用于具有相关类型的协议!这意味着使用具有相关类型的协议创建异构数组不再是一个限制!
// 🔴 Compile error in Swift 5.6: protocol ’Vehicle‘ can only be used as a generic constraint because it has Self or associated type requirements
// ✅ No compile error in Swift 5.7
let vehicles: [any Vehicle] = [
Car(),
Car(),
Bus(),
]
那有多酷?😃
这种改进不仅消除了“protocol can only be used as a generic constraint because it has Self or associated type requirements” 错误,而且还使在具有关联类型的协议上完成动态调度变得更加简单!但那将是另一天的一篇文章。
提示:
可在此处了解在具有关联类型的协议上实现动态调度是多么容易。
“any” 关键字限制
尽管看起来不错,但使用any
关键字创建的存在类型仍然有自己的局限性。一个主要限制是,我们不能使用==
运算符来比较2个存在类型的实例。
// Conform `Vehicle` protocol to `Equatable`
protocol Vehicle: Equatable {
var name: String { get }
associatedtype FuelType
func fillGasTank(with fuel: FuelType)
}
let myCar1 = createAnyVehicle(isPublicTransport: false)
let myCar2 = createAnyVehicle(isPublicTransport: false)
let isSameVehicle = myCar1 == myCar2 // 🔴 Compile error: Binary operator ’==‘ cannot be applied to two ’any Vehicle‘ operands
let myCar1 = createSomeVehicle()
let myCar2 = createSomeVehicle()
let isSameVehicle = myCar1 == myCar2 // ✅ No compile error
如果你仔细想想,这实际上是有道理的。如前所述,存在类型可以将任何具体类型存储在其“盒子”中。对编译器来说,存在类型只是一个“盒子”,它不知道盒子里有什么。因此,当编译器无法保证“盒子”的内容具有相同的底层具体类型时,编译器无法进行比较。
您应该注意的另一个限制是,存在类型不如不透明类型(使用一些关键字创建)有效。Donny Wals有一篇很棒的文章,详细讨论了这个问题,我强烈建议你去看看。
因此,尽管苹果对any
关键字做了很多改进,但如果不透明类型可以完成工作,仍然建议使用some
关键字。
结论
Swift 5.7中对some
和any
关键字的改进绝对是值得欢迎的。一方面,它极大地改善了范型代码的语法和可读性。另一方面,它为我们以更有效的方式编写范型代码开辟了新的方法。
我希望这篇文章能让您很好地了解any
和some
关键字。
感谢您的阅读。👨🏻💻