---
title: "Swift 6.1 新特性"
description: "Swift 6.1 正式发布！新特性包括支持列表尾随逗号、扩展 nonisolated 关键字优化并发编程、元类型键路径（Metatype Keypaths）、自动推断任务组类型、成员导入可见性修复、编译器警告精准控制，以及 Swift Testing 框架的增强（如范围验证和测试作用域）。此外，改进跨平台构建、模块化特性选项等细节优化，进一步提升代码可读性、安全性及开发效率。这些调整虽非颠覆性更新，却为 Swift 6.2 的更大革新奠定基础，助力开发者更高效地构建现代化应用。"
url: "https://hicc.pro/p/swift/whats-new-in-swift-6-1"
author: "tyler wang"
published: "2025-05-06T12:42:15.000Z"
updated: "2026-10-22T13:17:40.536Z"
category: "Swift"
tags:
  - "Swift"
  - "Swift 6.1"
image: "https://image.hicc.pro/pro/20250510131936FpVaifBBNT-Yo2T8d6pCz9997GRdb6VPFd47NHp3Xhv4tn47h.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# Swift 6.1 新特性

## 尾部逗号

[SE-0439](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0439-trailing-comma-lists.md)调整了Swift的语法，在数组，字典，字典，元组，函数调用，泛型参数，字符串插值任何`( )`，`[ ]`，`< >`逗号分隔列表中支持了尾部逗号。

例如：

```swift
func add<T: Numeric,>(_ a: T, _ b: T,) -> T {
    a + b
}

let result = add(1, 5,)
print(result,)
```

在正常使用中，会有类似下面的代码：

```swift
import Foundation

let message = "Reject common sense to make the impossible possible."

let range = message.range(
    of: "impossible",
    options: .caseInsensitive
)
```

在`range(of:options)`的调用函数，在Swift6.1之后，我们可以很方便的注释掉\`options: .caseInsensitive\` 这一整行而不会有语法错误。

注意：只有在括号等定界符中的列表才能用尾部逗号。下面的逗号场景是编译不通过的。

```swift
// enum IceCream {
//     case vanilla, chocolate, strawberry,
// }
```

## 元类型 Key path

[SE-0438](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0438-metatype-keypath.md)拓展了Keypath的能力，支持了类型中的静态属性。

```swift
struct WarpDrive {
    static let maximumSpeed = 9.975
    var currentSpeed = 8.0
}
```

&#x20;`currentSpeed`可以使用`let currentSpeed = \WarpDrive.currentSpeed` 来获取。

但是静态属性`maximumSpeed`需要使用`WarpDrive.Type`。

```swift
let maxSpeed = \WarpDrive.Type.maximumSpeed
```

当然可以在类型声明上使用：

```swift
let specificType: KeyPath<WarpDrive.Type, Double> = \.maximumSpeed
```

## 自动推导TaskGroup 子任务结果类型

[](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0442-allow-taskgroup-childtaskresult-type-to-be-inferred.md)

[SE-0442](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0442-allow-taskgroup-childtaskresult-type-to-be-inferred.md) 对TaskGroup做了一点改进，当使用`withTaskGroup()` 和 `withThrowingTaskGroup()` 时候，可以跳过`of`参数，应为Swift可以自动推导出子任务返回类型。

```swift
func printMessage() async {
   let string = await withTaskGroup { group in
       group.addTask { "Hello" }
       group.addTask { "From" }
       group.addTask { "A" }
       group.addTask { "Task" }
       group.addTask { "Group" }

       var collected = [String]()

       for await value in group {
           collected.append(value)
       }

       return collected.joined(separator: " ")
   }

   print(string)
}
```

在Swift 6.1之前，需要`withTaskGroup(of: String.self)`这么写。

## 容许nonisolated阻断全局actor推导

[SE-0449](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0449-nonisolated-for-global-actor-cutoff.md) 扩展了`nonisolated` 关键词，Swift 6.1之后可以应用到protocal，struct，class 和enum。让其阻断全局actor。

举个例子，我们一般会有个在特定actor的数据层，来保证数据的安全读写。

```swift
@MainActor
class DataController {
    func load() { }
    func save() { }
}
```

然后我们可以把它包裹在一个protocol，让遵循它的类型作为我们的controller，确保它们也在同样的actor。

```swift
@MainActor
protocol DataStoring {
    var controller: DataController { get }
}
```

现在任何遵循`DataStoring`的类型，都会自动推导为在同一个actor，就像`DataController` 我们可以不用`await`使用其方法。

```swift
struct App: DataStoring {
    let controller = DataController()

    init() {
        controller.load()
    }
}
```

上面的global actor 继承推导链条比较简单，但是某些情况可能需要阻断这种情况，因此从 Swift 6.1 开始，我们可以将协议、结构体、类和枚举标记为 `nonisolated`，允许它们选择退出从其他地方继承的参与者隔离。这意味着你需要使用 `await` 等方式来访问原本可以直接使用的参与者隔离数据，例如：

```swift
nonisolated struct App2: DataStoring {
    let controller = DataController()

    init() async {
        await controller.load()
    }
}
```

## 成员导入可见性

[SE-0444​](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md) 改进了 Swift 的模块导入规则，使代码行为更加明确和一致。虽然这可能导致少量编译错误，但修复起来非常简单，最终会让代码更加可靠。

在 Swift 6.1 之前，如果一个模块（Module）在某个文件中被导入，它的部分 API 可能会“泄漏”到项目的其他文件中，导致意外的行为或冲突。

假设有两个框架：

* `Map` 模块为 `Double` 添加了一个 `toRadians()` 方法（直接计算弧度）。
* `GeoKit` 模块也添加了 `toRadians()`，但它的版本会检查 `Double.nan` 并抛出错误。

```swift
// Maps 模块的扩展
public extension Double {
    func toRadians() -> Double { self * .pi / 180 }
}

// GeoKit 模块的扩展
public extension Double {
    func toRadians() throws -> Double { 
        guard !isNaN else { throw ConversionError.invalidNumber }
        return self * .pi / 180
    }
}
```

* 如果在 `ContentView.swift` 中只导入 `Maps`，`toRadians()` 会正常工作。
* 但如果在 `DetailView.swift` 中导入了 `GeoKit`，`GeoKit` 的 `toRadians()` 也会“泄漏”到整个项目，导致 `ContentView.swift` 中的代码突然可能抛出错误（因为编译器不知道用哪个版本）。

Swift 6.1 改进了这里，通过启用 **`MemberImportVisibility`** 编译标志（可在 Xcode 中配置），Swift 6.1 引入了更严格的导入规则：

* 模块的 API 仅在显式导入的文件中可用，不再“泄漏”到其他文件。
* 如果某个文件需要访问某个模块的功能，必须在该文件中显式导入它。

## 精细控制Swift编译器告警

[SE-0443](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0443-warning-control-flags.md)提案新增了对Swift编译器警告和错误的精细控制功能，比之前的"屏蔽警告"和"将警告视为错误"选项更灵活。

使用方法：

* 在Xcode的"Swift编译器&#x20;
  * 自定义标志"中添加`-print-diagnostic-groups`
  * 或命令行使用：`swiftc -print-diagnostic-groups main.swift`

效果示例：
当调用已弃用的函数时，警告信息会显示所属的诊断组（如`[DeprecatedDeclaration]`）：

```swift
@available(macOS, deprecated: 15.0, renamed: "sequoiaFunction")
func sonomaFunction() { }
sonomaFunction() // 警告会标注[DeprecatedDeclaration]
```

控制方式：

* 将特定警告升级为错误：添加`-Werror DeprecatedDeclaration`
* 保持特定警告不受影响：添加`-Wwarning 组名`

注意：

1. Xcode中每个标志需单独添加
2. 标志的顺序会影响最终效果
3. [完整文档](https://docs.swift.org/compiler/documentation/diagnostics)可参考Swift诊断组说明，但部分内容可能尚未同步到Swift 6.1

## 明确区分「Swift语言模式」与编译器版本

[SE-0441](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0441-formalize-language-mode-terminology.md)提案对Swift版本的描述方式进行了重要调整，严格区分**编译器版本**和**语言模式**。

当前许多用户使用Swift 6编译器，但运行在**Swift 5语言模式**下（为兼容性禁用新特性）。此前命令行和文档中笼统的"Swift版本"表述容易引发混淆。

新规要点：

* 统一采用**Swift语言模式**指代代码使用的语言标准版本
* 编译器版本与语言模式在文档和工具链中明确区分

（例如：编译器版本\=Swift 6，语言模式\=Swift 5）

## Swift Test: confirmation 支持range

[SE-0005](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0005-ranged-confirmations.md) 升级了`confirmation()`函数，不单支持单一固定值，还支持了range的完成次数。

例如，我们可能有一个简单的`NewsLoader`，来按需获取新闻，直到结束。

```swift
struct NewsLoader: AsyncSequence, AsyncIteratorProtocol {
    var current = 1

    mutating func next() async -> Data? {
        defer { current += 1 }

        do {
            let url = URL(string: "https://hws.dev/news-\(current).json")!
            let (data, _) = try await URLSession.shared.data(from: url)
            return data.isEmpty ? nil : data
        } catch {
            return nil
        }
    }

    func makeAsyncIterator() -> NewsLoader {
        self
    }
}
```

Swift 6.1 测试`confirmation()`函数可以这样写：

```swift
import Testing

@Test func fiveToTenFeedsAreLoaded() async throws {
    let loader = NewsLoader()

    await confirmation(expectedCount: 5...10) { confirm in
        for await _ in loader {
            confirm()
        }
   }
}
```

如果上述`confirm()`调用少于5次，或者大于10次，测试就算失败了。你可以使用半包的range：

```swift
@Test func atLeastFiveFeedsAreLoaded() async throws {
    let loader = NewsLoader()

    await confirmation(expectedCount: 5...) { confirm in
        for await _ in loader {
            confirm()
        }
   }
}
```

但是不能用没有最小值的range，类似`confirmation(expectedCount: ...10)`因为这会有歧义，不确定是10次（从1开始算），还是11次（从0开始算）。

## Swift 测试：从 #expect(throws:) 返回错误

[ST-0006](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0006-return-errors-from-expect-throws.md) 提案废弃了 `#expect(_:sourceLocation:performing:throws:)` 和 `#require(_:sourceLocation:performing:throws:)` —— 它们原本使用尾随闭包执行待测代码，再用第二个尾随闭包验证抛出的错误是否符合预期。

从 Swift 6.1 开始，`#expect(throws:)` 和 `#require(throws:)` 已更新为直接返回所检查类型的错误，允许你将期望验证和错误评估分开处理。

例如，你可能有一段代码禁止在清晨或深夜玩电子游戏：

```swift
enum GameError: Error {
    case disallowedTime
}

func playGame(at time: Int) throws(GameError) {
    if time < 9 || time > 20 {
        throw GameError.disallowedTime
    } else {
        print("Enjoy!")
    }
}
```

使用旧版废弃 API 时，检查具体错误类型的代码是这样的：

```swift
import Testing

@Test func playGameAtNight() {
    #expect {
        try playGame(at: 22)
    } throws: {
        guard let error = $0 as? GameError else { return false }
        // perform additional error validation here
        return error == .disallowedTime
    }
}
```

现在应迁移到新版写法，将期望验证和错误评估分离：

```swift
@Test func playGameAtNight() {
    // `error` will now be a GameError
    let error = #expect(throws: GameError.self) {
        try playGame(at: 22)
    }

    // perform additional validation here
    #expect(error == .disallowedTime)
}
```

## Swift 测试：测试作用域特性

[ST-0007](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0007-test-scoping-traits.md) 引入了测试作用域特性，提供细粒度、并发安全的共享测试配置访问，从而能够为测试精确设置运行环境，避免竞态条件。

例如，我们从一个 Player 结构体开始：

```swift
struct Player {
    var name: String
    var friends = [Player]()

    @TaskLocal static var current = Player(name: "Anonymous")
}
```

注意 `@TaskLocal` 属性标记的 `current` 属性。Swift 并发不允许创建共享可变状态，但 `@TaskLocal` 通过在每个任务中安全地放置共享实例来解决这个问题——一个任务内的代码可以安全读取共享值，而其他任务则拥有各自的 Player 值。

然后可以在生产代码中使用 `Player.current`，就像单例一样。实际上它并非单例，因为每个任务有独立的值，但这对代码透明。例如：

```swift
func createWelcomeScreen() -> String {
    var message = "Welcome, \(Player.current.name)!\n"
    message += "Friends online: \(Player.current.friends.count)"
    return message
}
```

这是常规的 Swift 并发代码。测试作用域的关键在于：Swift Testing 高度依赖并发执行测试，每秒可运行数千个单元测试。通过测试作用域，我们可以为特定测试设置自定义的 `Player.current` 值，确保测试代码使用精确值，同时避免并发测试间的共享状态冲突。

创建测试作用域需遵循两个协议：核心的 `TestTrait` 和 Swift 6.1 引入的 `TestScoping`。后者要求实现 `provideScope()` 方法来配置测试环境。例如：

```swift
import Testing

struct DefaultPlayerTrait: TestTrait, TestScoping {
    func provideScope(
        for test: Test,
        testCase: Test.Case?,
        performing function: () async throws -> Void
    ) async throws {
        let player = Player(name: "Natsuki Subaru")

        try await Player.$current.withValue(player) {
            try await function()
        }
    }
}
```

关键部分是 `Player.$current.withValue(player)`，它在调用测试函数时确保使用自定义的 `Player.current` 值。

为更好地集成，可添加 Trait 扩展：

```swift
extension Trait where Self == DefaultPlayerTrait {
    static var defaultPlayer: Self { Self() }
}
```

然后即可在测试中使用：

```swift
@Test(.defaultPlayer) func welcomeScreenShowsName() {
    let result = createWelcomeScreen()
    #expect(result.contains("Natsuki Subaru"))
}
```

若需配置多个任务局部值，有两种选择：

1. 若需同时配置，可嵌套 `withValue()` 调用。
2. 若需独立配置，可创建多个作用域并通过 `@Test(.firstScope, .secondScope)` 组合使用。Swift Testing 会按顺序执行作用域，后续作用域可覆盖先前设置的值。

测试作用域是对现有功能的补充，仍可使用 `init()` 和 `deinit()` 进行设置/清理。不同之处在于，它们允许按需为单个测试或整个测试套件选择配置。

此外，还有更多变化可能影响您的开发工作：

* [SE-0387](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md) 显著简化了跨平台编译流程——例如，现在可以直接在搭载 Apple Silicon 的 macOS 设备上构建面向 x86-64 架构的 Linux 二进制程序。
* [SE-0436](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0436-objc-implementation.md) 允许 Swift 替换来自 Objective-C 的实现代码，进一步提升与 Objective-C 的互操作性。
* [SE-0450](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0450-swiftpm-package-traits.md) 为 Swift 包引入了可选特性机制：包作者可以定义实验性 API 等可选功能模块，使用者在导入包时可自主选择启用所需特性。

***

整篇翻译自：https://www.hackingwithswift.com/articles/276/whats-new-in-swift-6-1

---

原文 / Source: <https://hicc.pro/p/swift/whats-new-in-swift-6-1>
