[SC] 可发送

发布: (2026年3月17日 GMT+8 04:42)
7 分钟阅读
原文: Dev.to

Source: Dev.to

问题

为什么编译器需要知道一个值是否线程安全?

编译器必须确保一个值能够在隔离域之间传递而不会导致数据竞争。如果该值不安全,编译器会发出警告或错误,以避免出现不可确定的行为。

什么是 isolation domain(隔离域),它有什么作用?

一个 隔离域 定义了一条边界,在此边界内可以安全地访问值或引用而不会产生数据竞争。共有三种类型:

类型描述
nonisolated默认域;不强加并发限制。
actor每个 actor 实例都有自己的域。
global actor多个类型、属性和函数共享的域(例如 @MainActor)。

nonisolated 代码对其他域状态有什么限制?

  • nonisolated 是默认的隔离域,对内部并发施加限制。
  • 可以毫无问题地修改同一类型中其他代码的状态。
  • 不能修改其他隔离域的状态。

无状态方法示例(可以从任何线程调用):

// ⚠️ `nonisolated` 是冗余的,因为它是默认域。
nonisolated func add(a: Int, b: Int) -> Int {
    a + b
}

func add(a: Int, b: Int) -> Int {
    a + b
}

为什么从外部访问 actor 的属性需要 await

actor 保证其所有存储属性和方法在单线程安全环境中执行,从而避免数据竞争。要从其他域访问这些数据,需要使用 await 来:

  1. 切换到 actor 的上下文。
  2. 等待 actor 释放其独占权。
actor Library {
    // 隔离属性;只能通过 `await` 读取。
    var books: [String] = []

    // actor 方法(从外部调用时隐式 async)。
    func addBook(_ title: String) {
        books.append(title)
    }

    func getBookList() -> [String] {
        books
    }

    // `nonisolated` 方法:可以不使用 `await` 调用。
    nonisolated func libraryName() -> String {
        "A library of books"
    }
}

使用示例:

let library = Library()

// ❌ 不能同步调用 actor 方法。
library.addBook("hola")   // 编译错误

Task {
    await library.addBook("Dopamine Nation")
    let books = await library.getBookList()
    print(books)               // ["Dopamine Nation"]

    // 直接读取属性(需要 await)。
    await library.books.forEach { print($0) }

    // ❌ 不能在 actor 外部修改属性。
    // library.books.append("Another")   // 错误
}

global actor(全局 actor)如 @MainActor 与普通 actor 有何区别?

  • 普通 actor:每个实例拥有自己的隔离域。
  • 全局 actor:所有标记了该 actor 的实例共享同一个域(例如 @MainActor)。当应用的多个部分需要在相同的并发约束下运行时,这非常有用。

公共 API 要被视为线程安全并符合 Sendable 协议,需要满足哪些条件?

当一个接口满足以下条件时,即被认为在隔离域之间是安全的:

  • 不公开可变的公共成员。
  • 实现了自己的内部锁机制(例如 NSLockDispatchQueue)。
  • 其可变成员采用 copy‑on‑write(如值类型)。

标准库中许多类型已经遵循 Sendable。编译器也可以推断隐式符合:

// ✅ 隐式符合,因为其所有成员都是 Sendable

成员是 Sendable

struct SomeStruct {
    var someIntValue: Int
}
// ❌ 不会隐式符合;类是引用类型。
class SomeClass {
    var someIntValue: Int = 0
}

背诵

  • 为什么带有 Int 属性的 struct 会隐式符合 Sendable,而具有相同属性的 class 则不符合?
    因为 struct 是值类型;复制时会创建一个独立的新实例,从而避免数据竞争。而 class 是引用类型,可能在多个隔离域中被同时修改。

  • 你能用自己的话解释一下,当一个值在两个隔离域之间传递时会发生什么吗?
    Swift 会检查该值是否符合 Sendable。如果符合,编译器就允许其转移,并保证不会对同一数据产生未同步的并发访问。

  • 什么时候会有意义…? (根据你的需求继续补充).

如何在 actor 中将方法标记为 nonisolated

如果不需要保护 actor 的状态,可以使用 nonisolated 标记。例如,当方法 返回某个标量或常量值 时。

Review

1. 公共 API 在哪些三种条件下可以安全地在并发域之间使用?

  1. 不可变性 – 暴露的值在创建后不能被修改。
  2. Sendable – 所有涉及的类型都遵循 Sendable 协议。
  3. 无副作用 – API 不会访问或修改可能在线程之间共享的外部状态。

2. Swift 6.2 对 Sendable 默认行为有什么改变?

Swift 6.2 引入了 nonisolated(nonsending),它允许函数和闭包 在不跨越隔离边界的情况下不必是 Sendable。这样,编译器在 nonisolated 上下文中不再自动强制符合 Sendable,从而更容易与不需要在并发域之间传递的代码进行互操作。

参考文献

  • Van der Lee, A. (2025). Swift Concurrency Course [在线课程]. avanderlee.com
0 浏览
Back to Blog

相关文章

阅读更多 »

现代 JS 讲座 async function

概述:async 函数返回一个 Async Function 对象。通过使用 async 和 await 关键字,异步处理可以比传统方式更简洁。

[SC] 在 SwiftUI 中执行 Tasks

搜索示例与 .task 有什么关系?重新使用 search_query: String 示例,其中在 500 ms 防抖后过滤一些条目……