Swift 6.1 有哪些新变化?
列表中的尾随逗号、元类型键路径、诊断分组等
Paul Hudson
今年春季发布的 Swift 为语言带来了多项令人欢迎的增补和改进,可为许多项目带来细小却重要的优化。
诸如“列表允许尾随逗号”这样的特性早已出现在许多开发者的愿望清单中;而对 nonisolated
的扩展使用,也能绕过 Swift 并发中常见的一些痛点。不过总体来说,这些变化属于“润色级”更新,为 Swift 6.2 中的更大改动铺平道路。
下面,咱们来逐条看看变化内容……
小贴士
想亲自试代码示例?可将本文下载为 Xcode Playground。
允许在逗号分隔列表中使用尾随逗号
SE-0439 调整了 Swift 语法:现在在数组、字典、元组、函数调用、泛型参数、字符串插值,乃至所有由圆括号 ()
, 方括号 []
, 尖括号 <>
包围的列表中,都可以写尾随逗号:
1 | func add<T: Numeric>(_ a: T, _ b: T,) -> T { |
现实中我们多半不会这样写函数,但在多行参数调用里就非常实用:
1 | let range = message.range( |
从 6.1 起,你可以整行注释掉 options:
仍能编译通过——因为上一行的尾随逗号被允许。
注意
尾随逗号仅限“有定界符”的场景;因此下面的代码仍无法编译:
1
2
3 // enum IceCream {
// case vanilla, chocolate, strawberry,
// }
元类型(Metatype)键路径
SE-0438 扩展键路径以支持类型的静态属性,进一步增强其表达能力。
1 | struct WarpDrive { |
TaskGroup 的 ChildTaskResult
类型可被推断
SE-0442 在使用 withTaskGroup
/ withThrowingTaskGroup
时,可省略 of:
参数,Swift 会根据返回值自动推断:
1 | func printMessage() async { |
用 nonisolated
阻止全局 Actor 推断
SE-0449 现可在协议、结构体、类、枚举层面标记 nonisolated
,以显式退出从其他地方继承而来的 Actor 隔离。
1 |
|
成员导入可见性
SE-0444 清理了 import 规则,配合新构建标志 **MemberImportVisibility
**,要求在每个 Swift 文件中显式写出所需模块的 import
。
这将修复“漏出”API 造成的名称冲突,但可能导致少量编译错误;解决方式是手动补齐 import。
更精确地控制编译警告
SE-0443 引入“诊断分组”概念,可针对特定 warning/error 精细化配置 -Werror
、-Wwarning
等标志。
- 在 Other Swift Flags 加
-print-diagnostic-groups
; - 观察编译输出末尾的
[GroupName]
; - 追加
-Werror GroupName
或-Wwarning GroupName
进行升级 / 降级。
顺序很重要:先设全局,再覆写分组,或反之,结果不同。
统一“语言模式”术语
SE-0441 明确区分“Swift 编译器版本”与“Swift 语言模式”。
许多人使用 Swift 6 编译器 却仍处于 Swift 5 语言模式;如今官方文件及命令行选项将使用 language mode 描述后者。
Swift Testing:基于范围的确认(Range‑based confirmations)
ST-0005 对 confirmation()
函数进行了升级,允许传入 完成次数区间,而不再局限于单一固定值。
示例:按需加载新闻源
假设我们有一个简单的 NewsLoader
,会按需抓取新闻,直到不再有新的源可取:
1 | struct NewsLoader: AsyncSequence, AsyncIteratorProtocol { |
在编写测试时,我们也许事先知道期望恰好加载 5 条新闻;但事实上,只要返回 至少 5 条 也能接受。自 Swift 6.1 起,confirmation()
现在可接受区间参数:
1 | import Testing |
- 若
confirm()
被调用 少于 5 次或多于 10 次,测试即失败。 - 你也可以用“半开区间”,例如确保至少调用 5 次:
1 | func atLeastFiveFeedsAreLoaded() async throws { |
⚠️ 不允许省略下界(如
confirmation(expectedCount: ...10)
),以避免歧义——无法确定是“最多 10 次(从 1 计数)”,还是“最多 11 次(从 0 计数)”。
Swift Testing:从 #expect(throws:)
返回错误对象
ST-0006 弃用了旧版#expect(_:sourceLocation:performing:throws:)
与 #require(_:sourceLocation:performing:throws:)
。
它们曾使用两个尾随闭包:第一个执行待评估代码,第二个检验捕获的错误。
自 Swift 6.1 起,新的 #expect(throws:)
与 #require(throws:)
直接返回被检查的错误类型,方便将“断言”与“错误验证”分离。
示例:限制游戏时间
1 | enum GameError: Error { |
旧 API(已弃用)
1 | import Testing |
新 API
1 | func playGameAtNight() { |
这些改动让 Swift 6.1 的测试 API 更加灵活、直观——无论是对可预期的调用次数设上限/下限,还是将错误校验解耦,均能编写出更清晰的测试代码。
Swift Testing:测试作用域特性(Test Scoping Traits)
ST-0007 引入了“测试作用域特性”,它为共享的测试配置提供了细粒度、并发安全的访问方式,使我们能够在不产生竞态条件的前提下,为特定测试构建精确的执行环境。
基本示例
1 | struct Player { |
请注意 current
属性上的 @TaskLocal
标记。
Swift 并发不允许直接创建共享的可变状态,而 @TaskLocal
通过在 每个任务内部 放置一个并发安全的共享实例来解决这一问题:同一任务中的代码可以安全地读取这个值,而其他任务则拥有各自独立的 Player
实例。
在实际代码中,你可以像使用单例一样访问 Player.current
(虽然它并不是真正的全局单例——每个并发任务都有自己的值):
1 | func createWelcomeScreen() -> String { |
到目前为止,这只是常规的 Swift 并发用法。接下来就轮到 测试作用域 发挥作用了:Swift Testing 默认并发执行大量单元测试。通过作用域,我们可以在某些测试外围包裹一个自定义的 Player.current
,从而保证该测试内部使用的值精确且隔离,不会与其他并发测试产生冲突。
创建测试作用域
要创建一个作用域,需要同时遵循两个协议:
- **
TestTrait
**(核心特性协议) - **
TestScoping
**(Swift 6.1 新增,负责具体作用域逻辑)
TestScoping
唯一必须实现的方法是 provideScope()
——它负责为测试配置环境。
1 | import Testing |
重点在最后一行:Player.$current.withValue(player)
会在整个测试执行期间,把 Player.current
设为我们自定义的值。
让特性更易用
为了让自定义特性与内置特性保持一致,可添加一个简短的扩展:
1 | extension Trait where Self == DefaultPlayerTrait { |
在测试中使用作用域
1 | func welcomeScreenShowsName() { (.defaultPlayer) |
只需在 @Test
标记中写 .defaultPlayer
,框架就会自动启用该作用域;运行 createWelcomeScreen()
时,Player.current
将是我们在作用域里设置的自定义值,而非默认值。
多个 Task-local 值
如果需要配置多个 Task-local 值,有两种方式:
- **嵌套
withValue()
**:若多个值必须同时生效,可将调用层层嵌套。 - 多个作用域:如果值之间可以独立组合,则可创建多个作用域,并在测试标签里写
@Test(.firstScope, .secondScope, .thirdScope)
。Swift Testing 会按列出顺序依次应用作用域,后面的作用域可以覆盖前面的设置。
与现有特性协同
测试作用域用于 补充 而非替代现有的 Swift Testing 机制——例如,你仍可在 init()
/deinit()
里执行自定义的测试准备与清理工作。
区别在于:作用域让我们可以对单个测试或整组测试按需开启环境配置,且在并发运行时天然避免竞态条件。
还有更多……
除了上文提到的内容,Swift 6.1 还有其他值得关注的更新:
- SE-0387:显著简化跨平台构建,例如在 Apple Silicon 的 macOS 上编译 x86-64 Linux 可执行文件。
- SE-0436:允许 Swift 替换 Objective-C 的实现代码。
- SE-0450:Swift Package 可声明“可选特性”,使用者可按需启用或移除(如实验性 API)。
总体而言,Swift 6.1 虽非“革命性”版本,但通过对子任务类型推断、导入可见性、元类型键路径等细节的打磨,使语言更加一致、易用。
Swift 6.2 预计将在 WWDC 25 登场,届时并发模型等方面将迎来进一步改进,值得期待。