---
title: "Swift 6.0新特性"
description: "Swift 6针对并发进行了重要更新，包括默认开启完全并发检查、移除误报数据竞争的告警、引入isolation region以确定代码的并发执行部分、简化并发编程、改进操作Objective-C框架时的并发能力、允许`async`函数隔离在调用者相同的actor、移除property wrapper的actor推断、开启strict concurrency for global variables、函数的默认值和函数本身在一样的隔离区等。此外，还引入了`count(where:)`新方法、Typed throws能力、参数包（Pack iteration）迭代、非连续元素集合方法、`import`声明上的访问级修饰符、不可复制类型更新、128b Int类型、`BitwiseCopyable`协议编译优化等技术关键点。"
url: "https://hicc.pro/p/swift/whats-new-in-swift-6"
author: "tyler wang"
published: "2024-06-27T23:48:02.000Z"
updated: "2025-12-14T07:45:17.000Z"
category: "Swift"
tags:
  - "Swift 6"
image: "https://image.hicc.pro/pro/20240713115903FodStGs7lisOhkYLlxYUBd49t8VeABogcnzFMpmkF5JoBp0Jy.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# Swift 6.0新特性

### 完全并发默认开启

Swift 6 针对并发做了不少的更新，其中最大的就是**默认打开了完全并发检查**，之前可选的就是能给项目更多的时间来调整代码。

Swift 6对并发检查做了进一步的改进，移除了大量5.10中误报数据竞争的告警。

最大的引入了[SE-0414](https://github.com/apple/swift-evolution/blob/main/proposals/0414-region-based-isolation.md "SE-0414")，定义了isolation region，来让编译器能够最终决定代码中不同部分能够并发执行。

这里核心的点在已存在的概念*可发送性sendability*，struct，常量属性的final clalss，actor 等这些`Sendable`类能够在并发环境中安全传递。

在Swfit 6之前的版本中，如果一个non-`sendable`的值从一个actor传递到另一个actor，会报一个并发检查的告警。例如下面，SwiftUI View body 在主actor上执行，SwiftUI view自己并不在主actor，这样就会很容易引起各种并发告警误报——在不会发生数据竞争的地方误报告警。

```swift
class User {
    var name = "Anonymous"
}

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .task {
                let user = User()
                await loadData(for: user)
            }
    }

    func loadData(for user: User) async {
        print("Loading data for \(user.name)…")
    }
}
```

Swift 6之前调用`loadData()`会有告警“passing argument of non-sendable type 'User' outside of main actor-isolated context may introduce data races.”。

在Swift 6之后t能够检测出，这里并没有多个`user`数据获取，因此也就不会有并发告警。

可发送的值，要么遵循`Sendable` ，要么就是Swift证明了不需要遵循。简化了并发编程。

这里还有其他的改进：

* [SE-430](https://github.com/apple/swift-evolution/blob/main/proposals/0430-transferring-parameters-and-results.md "SE-430") 增加了新的`sending`关键词，来让我们在隔离区（isolation region）传送值的时候使用。
  * This proposal extends region isolation to enable the application of an explicit `sending` annotation to function parameters and results. A function parameter or result that is annotated with `sending` is required to be disconnected at the function boundary and thus possesses the capability of being safely sent across an isolation domain or merged into an actor-isolated region in the function's body or the function's caller respectively.
* [SE-0423](https://github.com/apple/swift-evolution/blob/main/proposals/0423-dynamic-actor-isolation.md) 改进了操作Objective-C框架时候的并发能力
* [SE-0420](https://github.com/apple/swift-evolution/blob/main/proposals/0420-inheritance-of-actor-isolation.md) 容许我们让`async`函数隔离在调用者相同的actor。

还有些改动是，之前藏在特性开关中的。 例如，[SE-0401](https://github.com/apple/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md)移除了Swift 5.5引入的一个特性：property wrapper的actor推断。

之前，任何struct或者class使用了`@MainActor`装饰的属性包裹器都会自动变成`@MainActor`。如果你在SwiftUI中使用了`@StateObject`或者`@ObservedObject`整个view都会变成`@MainActor`。

例如，下面的view model，装饰`@MainActor`是一个好的做法：

```swift
@MainActor
class ViewModel: ObservableObject {
    func authenticate() {
        print("Authenticating…")
    }
}
```

在Swfit 6之后，你如果要在SwiftUI中使用`@StateObject`, 你必须也需要将view 也装饰为`@MainActor`：

```swift
@MainActor
struct LogInView: View {
    @StateObject private var model = ViewModel()

    var body: some View {
        Button("Hello, world", action: startAuthentication)
    }

    func startAuthentication() {
        model.authenticate()
    }
}
```

之前版本的Swift中不需要，是因为view中使用了`@StateObject`，整个view就自动赋予了`@MainActor`。

另一个Swift 6中被开启的是[SE-0412](https://github.com/apple/swift-evolution/blob/main/proposals/0412-strict-concurrency-for-global-variables.md)， 需要全局变量在并发环境中安全。

例如，全局的变量：

```swift
var gigawatts = 1.21
```

或者存储在类型中的静态变量

```swift
struct House {
    static var motto = "Winter is coming"
}
```

这些数据可以在任何时候都被获取到，这样在并发环境总就会不安全。 解决这个问题：

* 转换为sendable 常量，或者限制在global actor，例如`@MainActor`中。
* 上述不行，或者有其他的保守手段，可以标注为nonisolated.

```swift
struct XWing {
    @MainActor
    static var sFoilsAttackPosition = true
}

struct WarpDrive {
    static let maximumSpeed = 9.975
}

@MainActor
var idNumber = 24601

// Not recommended unless you're certain it's safe
nonisolated(unsafe) var britishCandy = ["Kit Kat", "Mars Bar", "Skittles", "Starburst", "Twix"]
```

还有一个开启的是[SE-0411](https://github.com/apple/swift-evolution/blob/main/proposals/0411-isolated-default-values.md)，函数的默认值和函数本身在一样的隔离区isolation。

例如，下面列子在之前版本是报错的。

```swift
@MainActor
class Logger {

}

@MainActor 
class DataController {
    init(logger: Logger = Logger()) {

    }
}
```

因为`DataController`和`Logger`  都限制在主actor，Swift现在认为创建`Logger()`也需要局限在主actor，这确实合理的。

更多Swift并发的知识，可以查看[https://www.massicotte.org/](https://www.massicotte.org/)

### `count(where:)`

[SE-0220](https://github.com/apple/swift-evolution/blob/master/proposals/0220-count-where.md)，引入了`count(where:)`的新方法。相当于`filter()`只是用来计数。这点很方便免去了很多时候为了计数而创建临时数组。

```swift
let scores = [100, 80, 85]
let passCount = scores.count { $0 >= 85 }
```

```swift
let pythons = ["Eric Idle", "Graham Chapman", "John Cleese", "Michael Palin", "Terry Gilliam", "Terry Jones"]
let terryCount = pythons.count { $0.hasPrefix("Terry") }
```

这个方法，所有遵循`Sequence`的类型都能使用。例如，set和字典dic也能使用。

### 类型化抛错

[SE-0413](https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md)引入了一种称之为“Typed throws”能力，可以指定函数抛出错误的类型。这就解决了Swift 错误处理中比较烦人的点：即使我们已知了所有可能的错误，我们还是一个通用的捕获错误的语句。

例如，我们可以定义一个`CopierError`的错误。

```swift
enum CopierError: Error {
    case outOfPaper
}
```

我们创建一个`Photocopier`结构体有一个`copy`方法。我们可以在没有足够纸张的时候不抛出通用的`throws`而是指定错误`throws(CopierError)`。

```swift
struct Photocopier {
    var pagesRemaining: Int

    mutating func copy(count: Int) throws(CopierError) {
        guard count <= pagesRemaining else {
            throw CopierError.outOfPaper
        }

        pagesRemaining -= count
    }
}
```

注：`throws`表示任意类型的错误，`throws(OneSpecificErrorType)` ，`throws(A, B, C)` 表示指定错误。

捕获错误也会简单很多：

```js
do {
    var copier = Photocopier(pagesRemaining: 100)
    try copier.copy(count: 101)
} catch CopierError.outOfPaper {
    print("Please refill the paper")
}
```

升级之后，`throws(any Error)` 等价于`throws`，`throw(Never)`等于不抛出错误，这个可能有点费解，但是对\`rethrows\`来说就很明确了，函数会抛出函数参数抛出的任意错误。

例如，Swfit 6中的`count(where:)`方法接受一个闭包来执行匹配计算。这个闭包也可以抛出错误，这样`count(where:)`也会抛出同样的错误：

```swift
public func count<E>(
    where predicate: (Element) throws(E) -> Bool
) throws(E) -> Int {
```

当闭包不抛出错误的时候，`throws(E)`就是`throws(Never)`。也就意味着`count(where:)`不抛出错误。

虽然这个功能能吸引人，当时如果未来抛错可能会变化的时候，就不是一个好选择。特别是对于库中的代码，等于是锁死了你未来抛错的可能。

甚至来说Typed throws可能更加适用于对嵌入式Swift的场景，这里性能和可预测性非常重要。

### 参数包（Pack iteration）迭代

[SE-0408](https://github.com/apple/swift-evolution/blob/main/proposals/0408-pack-iteration.md)引入了参数包的迭代，以此支持了Swift 5.9引入的[参数包](/p/swift/whats-new-in-swift-5-6-to-5-10#值和类型参数包)迭代loop的能力。

这也就是间接地让可以实现任意数量的元组比较。

```swift
func == <each Element: Equatable>(lhs: (repeat each Element), rhs: (repeat each Element)) -> Bool {
    for (left, right) in repeat (each lhs, each rhs) {
        guard left == right else { return false }
    }
    return true
}
```

简单来说，[SE-0015](https://github.com/apple/swift-evolution/blob/main/proposals/0015-tuple-comparison-operators.md)只是简单支持了最多6个元素元组使用`==`比较，而SE-0408算是去除了这个限制。

### 非连续元素集合方法

SE-0270引入了新的集合方法，来支持更复杂的操作，例如移动删除不连续的多个元素。

这个改变是基于`RangeSet`的新类型来实行，和`IndexSet`很像，只是元素是任意`Comparable`元素，而不是只是简单的int。

很多Swift API也己经升级到了`RangeSet`。

```swift
struct ExamResult {
    var student: String
    var score: Int
}

let results = [
    ExamResult(student: "Eric Effiong", score: 95),
    ExamResult(student: "Maeve Wiley", score: 70),
    ExamResult(student: "Otis Milburn", score: 100)
]
```

我们可以得到所有85%以上分数的index`RangeSet`。

```swift
let topResults = results.indices { student in
    student.score >= 85
}
```

然后如果你需要得到这些学生，可以使用新的`Collection` subscript：

```js
for result in results[topResults] {
    print("\(result.student) scored \(result.score)%")
}
```

上述会得到一个新的`DiscontiguousSlice`类型，和`Slice`很像性能的考量在新的集合中存放的是元素的引用，而`DiscontiguousSlice`中的坐标是不连续的。

`RangeSet`set的点在于，它支持来自于`SetAlgebra`协议的方法，例如`union()`, `intersect()`和`isSuperset(of:)`。也因此将一个range插入另一个range，相同元素会merge而不是重复。

### `import` 声明上的访问级修饰符

[SE-0409](https://github.com/apple/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md)增加了标注import声明访问级别的能力，例如可以标注为，`private iimport SomeLibrary`。

这点很有用，例如库并不想对使用者暴漏内部包裹的依赖。标注`internal`，`private` Swift就不会为其隐藏的库构建声明，除非我们特意声明为`public`。

Swfit 6 默认处理为`internal`，以前的版本处理为`public`。

### 不可复制类型更新

Swift 6升级了，Swift 5.9引入的[不可复制类型](/p/swift/whats-new-in-swift-5-6-to-5-10#不可复制的结构体struct-和枚举-enum)。

第一个升级是[SE-427](https://github.com/apple/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md)，一次引入了很多改进，最大的改进是每个struct，class，enum，泛型参数，protocol，Swift 6都自动遵循`Copyable`协议。除非你特意指定为`~Copyable`。

这也就意味着，不可复制类型也可以遵循也标注了`~Copyable`的协议。（`Copyable`类型可以遵循不可复制协议\`）。

[SE-0429](https://github.com/apple/swift-evolution/blob/main/proposals/0429-partial-consumption.md)支持了不可复制类型局部消费的能力。

之前一个不可复制类型和另一个同样不可复制类型交互的时候很有问题，例如下面的很常规的代码在之前也是不合法的：

```js
struct Package: ~Copyable {
    var from: String = "IMF"
    // Message也是~Copyable
    var message: Message

    consuming func read() {
        message.read()
    }
}
```

现在合法了，当然前提是类型不能有deinit。

另一个改进是[SE-0432](https://github.com/apple/swift-evolution/blob/main/proposals/0432-noncopyable-switch.md)，支持了在switch判断中借用不可复制类型。在之前的\`where\`闭包中是不可以的。

```swift
enum ImpossibleOrder: ~Copyable {
    case signed(Package)
    case anonymous(Message)
}
```

里面的`Package`和`Message`都是不可复制的，因此enum`ImpossibleOrder`也得是不可复制的。

Swift 6之后，我们可以对实现switch的模式匹配了。

```swift
func issueOrders() {
    let message = Message(agent: "Ethan Hunt", message: "You need to abseil down a skyscraper for some reason.")
    let order = ImpossibleOrder.anonymous(message)

    switch consume order {
    case .signed(let package):
        package.read()
    case .anonymous(let message) where message.agent == "Ethan Hunt":
        print("Play dramatic music")
        message.read()
    case .anonymous(let message):
        message.read()
    }
}
```

### 128b Int 类型

[SE-0425](https://github.com/apple/swift-evolution/blob/main/proposals/0425-int128.md)引入了 `Int128`和`UInt128`，这点无需多言。

```swift
let enoughForanybody: Int128 = 170_141_183_460_469_231_731_687_303_715_884_105_727
```

### `BitwiseCopyable`

[SE-0426](https://github.com/apple/swift-evolution/blob/main/proposals/0426-bitwise-copyable.md)引入了`BitwiseCopyable`协议，就是为了让所遵循类型能够到更优化的编译后代码。

大部分情况下你不需要手动来开启，Swift 会为 属性也都是按位拷贝的struct 和 enum自动开启，按位拷贝类型包括内置的整形，浮点数，`Bool`,`Duration`,`StaticString`等。

但是如果在库代码中，如果自动应用了`BitwiseCopyable`协议，在未来改变了类型，可能会导致问题，Swift基于这一点，在`public`导出或者`package`可见类型没有自动使用了`BitwiseCopyable`，除非这些类型标注为\`@frozen\`。

当然如果你可以使用`~BitwiseCopyable`来限制关闭。

```swift
@fronzen
public enum CommandLine : ~BitsiseCopyable {
  
}
```

上述虽然`public`也有`@frozen`，使用了`~BitwiseCopyable`，Swift，就会取消编译优化。

注：取消`BitwiseCopyable`需要直接在类型声明上使用`~BitwiseCopyable`，在扩展上使用不起作用。

***

整体翻译：https://www.hackingwithswift.com/articles/269/whats-new-in-swift-6

---

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