前言
虽然 swift 6 已经在地平线上浮现,但 5.x 版本仍然有很多新功能-更简单的 if 和 switch 用法、宏、非可复制类型、自定义 actor 执行器等等都将在 swift 5.9 中推出,再次带来了一个巨大的更新。
在本文中,将介绍这个版本中最重要的变化,提供代码示例和解释,以便可以自行尝试。需要在 xcode 14 中安装最新的 swift 5.9 工具链,或者使用 xcode 15 beta。
if 和 switch 表达式
se-0380 引入了在多种情况下将 if 和 switch 用作表达式的能力。这会产生一些在一开始可能会让人感到惊讶的语法,但总体上它确实帮助减少了语言中一些额外的语法。
作为一个简单的例子,我们可以根据条件将变量设置为“ pass ”或“ fail ”:
let score = 800
let simpleresult = if score > 500 { "pass" } else { "fail" }
print(simpleresult)
或者可以使用 switch 表达式来获取更广泛的值,就像下面这样:
let complexresult = switch score {
case 0...300: "fail"
case 301...500: "pass"
case 501...800: "merit"
default: "distinction"
}
print(complexresult)
不需要在某个地方分配结果以使用这种新的表达式语法,实际上它与 swift 5.1 的 se-0255 非常搭配,该特性允许我们在返回一个值的单表达式函数中省略 return 关键字。
所以,由于 if 和 switch 现在都可以作为表达式使用,我们可以编写一个像这样的函数,在所有四种可能的情况下都不使用 return 关键字:
func rating(for score: int) -> string {
switch score {
case 0...300: "fail"
case 301...500: "pass"
case 501...800: "merit"
default: "distinction"
}
}
print(rating(for: score))
你可能会认为这个特性使得 if 更像三元条件运算符,至少在某种程度上是对的。例如,我们可以像这样编写之前的简单 if 条件:
let ternaryresult = score > 500 ? "pass" : "fail"
print(ternaryresult)
然而,它们并不完全相同,特别是有一个地方可能会让你困惑 - 你可以在下面的代码中看到:
let customerrating = 4
let bonusmultiplier1 = customerrating > 3 ? 1.5 : 1
let bonusmultiplier2 = if customerrating > 3 { 1.5 } else { 1.0 }
这两个计算都产生了一个值为 1.5 的 double,但是注意每个计算的备选值:对于三元选项,是写了 1,而对于 if 表达式,是写了 1.0。
这样做是有原因的:当使用三元运算符时,swift 同时检查两个值的类型,因此自动将 1 视为 1.0,而对于 if 表达式,两个选项是独立进行类型检查的:如果我们在一个情况下使用 1.5,在另一个情况下使用 1,那么我们将返回一个 double 和一个 int,这是不允许的。
value 和 type 参数包
se-0393、se-0398 和 se-0399 结合在一起形成了 swift 中使用可变泛型的一系列改进的复杂结构。
这是一个相当高级的特性,用一种能够吸引大多数人注意的方式概括一下:这几乎肯定意味着 swift 中的旧的 10 个视图限制即将消失。
这些提案解决了 swift 中一个重要的问题,即泛型函数需要指定数量的类型参数。这些函数仍然可以接受可变参数,但最终仍然必须使用相同的类型。
举个例子,我们可以有三个不同的结构体,表示程序的不同部分:
struct frontenddev {
var name: string
}
struct backenddev {
var name: string
}
struct fullstackdev {
var name: string
}
在实际情况中,这些结构体可能具有更多使它们成为独特类型的属性,但你可以理解这个例子 - 存在三种不同的类型。
我们可以这样创建这些结构体的实例:
let johnny = frontenddev(name: "johnny appleseed")
let jess = frontenddev(name: "jessica appleseed")
let kate = backenddev(name: "kate bell")
let kevin = backenddev(name: "kevin bell")
let derek = fullstackdev(name: "derek derekson")
当需要开始工作时,我们可以使用以下简单的函数将开发人员配对:
func pairup1<t, u>(firstpeople: t..., secondpeople: u...) -> ([(t, u)]) {
assert(firstpeople.count == secondpeople.count, "you must provide equal numbers of people to pair.")
var result = [(t, u)]()
for i in 0..<firstpeople.count {
result.append((firstpeople[i], secondpeople[i]))
}
return result
}
该函数使用两个可变参数接收一组第一组人和一组第二组人,并将它们作为数组返回。
现在我们可以使用它将可以一起开展后端和前端工作的程序员配对:
let result1 = pairup1(firstpeople: johnny, jess, secondpeople: kate, kevin)
到目前为止,这些都是老的内容,现在我们开始进入有趣的部分:假设 derek 是一个全栈开发人员,可以作为后端开发人员或前端开发人员工作。然而,如果我们尝试使用 johnny, derek
作为第一个参数,swift 将拒绝构建我们的代码 - 它需要所有第一组人和第二组人的类型相同。
解决这个问题的一种方法是使用 any
抛弃所有类型信息,但是参数包使我们能够更加优雅地解决这个问题。
刚开始时,语法可能会有点复杂,所以我先展示代码,然后再解释它。请看以下代码:
func pairup2<each t, each u>(firstpeople: repeat each t, secondpeople: repeat each u) -> (repeat (first: each t, second: each u)) {
return (repeat (each firstpeople, each secondpeople))
}
这里有四个独立的部分,我们逐一来解释:
<each t, each u>
创建了两个类型参数包,t 和 u。repeat each t
是一个参数包展开,它将参数包展开为实际值 - 这相当于t...
,但避免了使用...
作为运算符时产生的某些混淆。- 返回类型表示我们将返回 t 和 u 的成对程序员元组的重复。
- 我们的
return
关键字才是真正起作用的地方:它使用参数包展开表达式从 t 中取一个值,从 u 中取一个值,将它们放在返回值中组合起来。
返回类型自动确保的 t 和 u 类型具有相同的结构 - 具有相同数量的内部项。因此,与我们在第一个函数中使用的 assert()
不同,如果尝试传入两组不同大小的数据,swift 将简单地发出编译器错误。
有了这个新函数,我们现在可以将 derek 与其他开发人员配对,例如:
let result2 = pairup2(firstpeople: johnny, derek, secondpeople: kate, kevin)
实际上,我们实现了一个简单的 zip()
函数,这意味着我们可以写出这样的荒谬代码:
let result3 = pairup2(firstpeople: johnny, derek, secondpeople: kate, 556)
它尝试将 kevin 与数字 556 配对,显然是没有意义的。这就是参数包的优势所在,因为我们可以定义以下协议:
protocol writesfrontendcode { }
protocol writesbackendcode { }
然后添加一些遵循关系:
frontenddev
应该遵循writesfrontendcode
。backenddev
应该遵循writesbackendcode
。fullstackdev
应该同时遵循writesfrontendcode
和writesbackendcode
。
现在,可以给我们的类型参数包添加约束:
func pairup3<each t: writesfrontendcode, each u: writesbackendcode>(firstpeople: repeat each t, secondpeople: repeat each u) -> (repeat (first: each t, second: each u)) {
return (repeat (each firstpeople, each secondpeople))
}
这意味着只有合理的配对才能发生 - 无论是否是全栈开发人员,我们始终会得到一个能够编写前端代码的人与一个能够编写后端代码的人配对。
为了让你更容易理解,我们在 swiftui 中也有类似的情况。我们经常希望能够创建具有多个子视图的视图,如果我们只使用单一的视图类型(例如 text
),那么类似于 text...
的写法将非常好用。但是,如果我们希望有一些文本,然后是图像,然后是按钮等等 - 任何非均匀布局都将不可能实现。
尝试使用 anyview...
或类似的方式来抹除类型会丢失所有类型信息,因此在 swift 5.9 之前,通过创建大量函数重载来解决此问题。例如,swiftui 的视图构建器具有 buildblock()
重载,可以组合两个视图、三个视图、四个视图等等,一直到 10 个视图 - 但没有更多,因为它们需要划定一个限制。
因此,在 swiftui 中存在 10 个视图的限制。希望这个限制在即将发布的 swift 5.9 中消失。
发表评论