Xcode 14 beta 3
Swift 5.7
简化的可选绑定(SE-0345)
对可选类型解包时无需显式绑定,写法更简洁:
1 | let s1: String? = "s1" |
更强大的类型推断
默认表达式的类型推断(SE-0347)
Swift 现在支持给泛型参数赋予默认值,并且能根据上下文推断泛型参数的具体类型。
1 | func compute<C: Collection>(_ values: C = [0, 1, 2]) { } |
多语句闭包的类型推断(SE-0326)
以前的多语句闭包必须写明参数和返回值类型:
1 | let _ = [-1, 0, 1].map { v -> String in |
现在,编译器会自动推断:
1 | let _ = [-1, 0, 1].map { |
更灵活的正则表达式
正则类型(SE-0350)
Swift 5.7 新增了正则类型 Regex<Output>
,用于便捷地构建正则表达式。
1 | let s = "Stay hungry, stay foolish." |
正则构造器 DSL(SE-0351)
正则表达式简洁有力,但难以书写。因此 Swift 提供了 DSL 供我们使用,便于方便地书写正则表达式。比如下面示例代码中的 OneOrMore(.word)
表达的意思和 /[A-Za-z0-9]+/
是一样的。
1 | **import RegexBuilder** |
Regex 还支持使用别名和下标来获取匹配结果:
1 | let ref1 = Reference(Substring.self) |
另外,RegexBuilder 中的 buildPartialBlock 实现了基于结果生成器的重载。这是 SwiftUI 喜闻乐见的,因为此前的 ViewBuilder 最多只能从 10 个子 view 构建,但 buildPartialBlock
可以突破这个限制。这和 reduce
函数有点类似,在前一个生成的结果基础上,继续累积新的值。
更多相关 API 请查看:RegexBuilder。
正则字面量(SE-0354)
Swift 支持使用字面量直接构建正则表达式,构建方式非常简单,只需要将表达式置于两个 /
之间:
1 | let regex4 = /[Ss]tay/ |
字面量构建的正则表达式同样支持使用别名对匹配结果进行引用:
1 | let regex5 = /Stay (?<s1>.+), stay (?<s2>[A-Za-z0-9]+)./ |
值得注意的是,基于字符串构建的 Regex 类型必须在运行时才能对该字符串进行正确解析。而正则字面量在编译期就能被编译器诊断出错误,这也是我们应该优先使用正则字面量的原因。下面是 Regex 类型和字面量结合使用的示例:
1 | let regex6 = Regex { |
基于正则的字符串处理算法(SE-0357)
除了上面提到的 matches(of:)
、wholeMatch(in:)
,Swift 中的集合类型许多原有的方法也提供了对 Regex 的支持。比如:
1 | printLog(s.replacing(/[Ss]tay/, with: "be")) |
阐明了非隔离异步函数的执行(SE-0338)
在此之前,在 g
中调用 f
时,f
可能会 actor 上执行并造成长时间的阻塞。
而现在所有的非隔离异步函数的执行,都会在全局的协作并发池上执行。当然,从 actor 切换至全局并发池执行时,程序依然会进行 Sendable
检查。比如,在 g
中调用 f
时,如果 c 没有实现 Sendable
,编译器会发出警告。
1 | class C { } |
新的时间 API (SE-0329)
Swift 5.7 提供了一种新的标准化时间组件,由以下三部分组成:
- Clock:基于 Clock 协议实现,是一种计算时间的机制,定义了现在以及将来某个指定的时间点唤醒工作的方式。
- Instant:基于 InstantProtocol 协议实现,表示某个时间点。
- Duration:基于 DurationProtocol 协议实现,表示两个时间点之间的间隔。
Clock 协议定义如下,其中的关联类型 Instant
遵循 InstantProtocol
协议,而另一个关联类型 Duration
和 InstantProtocol
协议中的 Duration: DurationProtocol
的类型保持一致:
1 | 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) (macOS |
系统内置了两种 Clock:
- ContinuousClock:系统睡眠时,仍能计时。
- SuspendingClock:系统睡眠时,停止计时。
两种 Clock 使用方式一样,这里以 ContinuousClock 为例。比如用来计算某个同步操作的耗时:
1 | let clock = ContinuousClock() |
Clock 还能用来计算异步事件的耗时,Task 也新增了对 Clock 的支持:
1 | func delayWork() async throws { |
如果任务在睡眠时间结束前就结束了,会抛出 CancellationError
类型的错误。
不透明类型增强了使用范围(SE-0341)
此前,不透明类型只能用于返回值。现在,我们还可以将其用于属性、下标、函数参数以及结构化的返回类型(元组、数组、闭包等)。
1 | func tuple(_ v1: some View, _ v2: some View) -> (some View, some View) { |
主要关联类型以及轻量级同类型要求(SE-0346)
协议支持多个关联类型,使用尖括号声明(类似泛型写法)的则是主要关联类型。
我们来看看 Collection
协议最新的定义:
1 | public protocol Collection<Element> : Sequence { |
这里的 Element
,就是主要关联类型。借助这一特性以及增强了使用范围的不透明类型,在使用具有主要关联类型的协议时,写法可以更优雅。比如下面这个用于比较两个集合的函数:
1 | func compare<C1: Collection, C2: Collection>(_ c1: C1, _ c2: C2) -> Bool |
可以写得更简洁易读:
1 | func compare<E: Equatable>(_ c1: some Collection<E>, _ c2: some Collection<E>) -> Bool { } |
实际上,以前类似的泛型写法 T where T: P, T.E: V
一般都可以简写为 some P<V>
。
关于存在类型的改进
所有协议都可以作为存在类型使用(SE-0309)
Swift 5.6 之前,我么经常遇到协议相关的编译错误:
1 | Protocol can only be used as a generic constraint because it has 'Self' or associated type requirements |
通常我们的解决方法是将协议作为泛型约束来解决,Swift 5.6 为此引入了存在类型(一个能够容纳任意遵循某个协议的具体类型的容器类型),并新增了 any
关键字来进行标记。这一特性在 Swift 5.7 中全面解锁,所有的协议都可以使用 any
关键字来进行修饰。
值得注意的是,存在类型会导致性能损耗,any
关键字的主要作用其实是为了提醒我们它带来的潜在副作用,因此我们应该尽量避免使用它,除非你真的需要一个动态的类型。
隐式打开的存在的类型(SE-0352)
前面我们提到存在类型是一种容器类型,它只有在运行时才能将容器打开获取到内部的具体类型。这会导致如下的代码报错:
1 | protocol P { |
因为泛型约束,takeP
在入参时需要传入一个实现协议 P
的具体类型。而 test
中的 p
是存在类型,它是一个容器类型,其内部的具体类型可以动态改变,并且只有在运行时才能获取到真正的具体类型。所以,我们会看到如上的编译错误。
但现在这个错误不存在了,Swift 赋予了存在类型隐式打开的特性。在 test
中将 p
传入 takeP
时,p
容器内部的具体类型会被自动取出,然后被传递至 takeP
函数。这个自动拆箱的过程,有点类似可选类型中的隐式解包。
受约束的存在类型(SE-0353)
具有主要关联类型的协议可以用于存在类型,通过主要关联类型对其进行约束。
比如我们将某个包含整数的集合转换称数组类型:
1 | func mapNumbers(_ c: any Collection<Int>) -> [Int] { |
分布式 actor
分布式 actor 主要用于服务端,有兴趣的读者可以参考:SE-0336、SE-0344。
本文仅是抛砖引玉,关于 Swift 5.7 更详细的变更请参考: Swift/CHANGELOG.md
文中涉及源码参考:Source code。