Swift - Sendable (not just Sendable)

Sendable 解读

重点:(了解全貌,直到最后一刻..)

  1. Swift 的 Sendable 是**针对类型(type)**的,而不是单个属性。

  2. StringIntDoubleBoolURL天然 Sendable,不需要你额外标记。

  3. Sendable 的定义是:类型是否可以安全跨线程传递(跨隔离域传递)

  4. Sendable 并不会阻止你对可变属性并发读写,它只保证"值本身是安全传递的"。

  5. SO 关键点:Sendable 是"传递安全",不是"访问安全 (但这里还需要进一步区分..)"

Sendable 协议在 Swift 类型系统中的普适性:

Sendable 不仅仅用于标记闭包,它还可以标记任何你希望能在并发域(Task 或 Actor)之间安全传递或共享的类型。

简而言之,Sendable 可以标记:

  1. 类型 (Types): 结构体(Struct)、枚举(Enum)、类(Class)、Actor。

  2. 函数/闭包类型 (Function/Closure Types): 确保闭包本身及其捕获的行为是线程安全的。


🚀 Sendable 可以标记的五种主要目标

1. 结构体 (Structs) 和 枚举 (Enums)

如果一个值类型的所有存储属性或关联值都是 Sendable 的,那么该类型就自动满足 Sendable 协议。如果不是自动满足(例如,包含了非 Sendable 的属性),你可以显式地标记它。

swift 复制代码
// 示例:一个结构体,如果其属性是 Sendable,则它自动是 Sendable
struct SafeConfiguration: Sendable {
    let timeout: Double // Double 是 Sendable
    let apiKey: String  // String 是 Sendable
}

// 示例:一个枚举,如果其关联值是 Sendable,则它自动是 Sendable
enum ResultState<T: Sendable>: Sendable { // T 必须是 Sendable
    case success(T)
    case loading
    case failure(Error) // Error 类型也是 Sendable 的
}

2. 类 (Classes)

对于引用类型(class),Sendable 的要求更严格,以防止多个引用同时修改共享状态。

  • Actor: actor 类型默认自动满足 Sendable 协议,因为 Actor 内置了对自身可变状态的隔离和同步机制。

    swift 复制代码
    actor DataCache: Sendable { // 自动满足 Sendable
        private var dataStore: [String: Data] = [:]
    }
  • Final Class: 只有 final 类才能满足 Sendable 协议。它要求:

    1. 所有存储属性都是 Sendable 的。

    2. 类不能被继承。

    3. (如果需要)必须使用同步机制(如 NSLock)来保护可变属性。

swift 复制代码
// 如果这个类内部使用锁来保护其可变状态
final class ThreadSafeCounter: @unchecked Sendable { // 假设内部有锁,所以使用 @unchecked Sendable
    private var value = 0
    private let lock = NSLock()

    func increment() {
        lock.lock()
        defer { lock.unlock() }
        value += 1
    }
}

3. 函数和闭包类型 (Function/Closure Types)

正如你示例中看到的,@Sendable 是一个类型属性,用于修饰函数类型,确保闭包可以安全地跨越并发域。

swift 复制代码
// 定义一个接受 Sendable 闭包的函数
func executeAsync(work: @Sendable () -> Void) {
    // ... 将 work 提交到一个后台 Task 中
}

// block的示例:
let progressHandler: (@Sendable (Double) -> Void)?

4. 泛型类型约束 (Generic Type Constraints)

你可以使用 Sendable 作为泛型参数的约束,确保只有并发安全的数据类型才能被用在你的通用结构中。

swift 复制代码
// 确保这个队列只能存储 Sendable 的元素
struct ConcurrentQueue<Element: Sendable>: Sendable {
    // ...
}

5. 协议 (Protocols)

你可以使一个协议继承 Sendable,以强制要求所有遵循该协议的类型都必须是并发安全的。

swift 复制代码
protocol ConcurrentModel: Sendable {
    var id: String { get }
    func validate() async throws
}

@unchecked Sendable 解读

swift 复制代码
private final class UploadContext: @unchecked Sendable {
    let continuation: CheckedContinuation<Data, Error>
    let resumeKey: URL
    let progressHandler: (@Sendable (Double) -> Void)?

    init(continuation: CheckedContinuation<Data, Error>, resumeKey: URL, progressHandler: (@Sendable (Double) -> Void)? = nil) {
        self.continuation = continuation
        self.resumeKey = resumeKey
        self.progressHandler = progressHandler
    }
}

🟦 那么:为什么还要用 @unchecked Sendable

因为 Swift 6 的 Sendable 检查会阻止:

  • class 传跨线程

  • actor 内部持有非 Sendable 成员

  • continuation 存在非 Sendable 上下文

  • delegate 回调流入 actor 时不安全的数据捕获

当你告诉 Swift:

"这个 class 是 Sendable,不要报错。"

"编译器别拦我,我自己负责确保这个引用类型不会被多个线程同时访问。"

这里👉,@unchecked Sendable 的作用不是为了让访问变安全,而是为了告诉编译器:

  • "我知道它可能是不安全的,但我确保实际使用场景 不会有并发读写。"

🟩 那么问题的重点来了:

🟦 在 APIService(actor)中,这个 class 是否安全?

安全。100% 安全。

原因是:

✔ Actor 提供了"隔离":

所有对 UploadContext 的读写都发生在 同一个 actor 隔离域 内:

swift 复制代码
actor APIService { 
    private var contexts: [Int: UploadContext] 
// actor 内部,只有一个线程能访问 
}

因此不会并发访问到同一个 UploadContext 实例。

重点:如果 class UploadContext 不加上 @unchecked Sendable ,无法在 actor APIService 中使用,直接报错⚠️⚠️

或者:

  • 将 UploadContext 变成 struct(值类型) → 自动 Sendable

  • 将 UploadContext 变成 actor(完全隔离) → 最安全

🟥 2. 为什么 UploadContext class 不加 @unchecked Sendable 会报错?

因为:

  • class 是引用类型

  • Swift 的 Sendable 自动推断规则认为 class 不是 Sendable

  • 你将它存入 actor 的属性中,例如:

swift 复制代码
actor APIService {
    private var uploadContexts: [Int: UploadContext] = [:]  // ❌ 报错
}
报错:Type 'UploadContext' does not conform to 'Sendable'

那为什么要用 @unchecked Sendable

因为:

  • 你知道 UploadContext 只会在 actor 内部被访问

  • 对它的所有读写都由 actor 的序列化执行保障

  • 即使 class 是引用类型,也不会被多线程并发访问

  • 因此它是逻辑上安全的

为什么是 @unchecked,而不是普通 Sendable?

因为 class 不能自动推断 Sendable

(引用类型可能被共享,可能同时被多个线程访问)

Sendable 协议要求:class 必须保证所有存储属性自身是 Sendable 且不会被并发访问`

这是不可能自动证明的。所以 Swift 要求你明确声明:

final class UploadContext: @unchecked Sendable

@unchecked 的意思:

  • 我将在未来的使用中保证并发安全

  • 你这个编译器还有你这个编译器统统脱掉裤子,我不检查(我不看)

  • 风险自负

APIService 中这些方式会触发 Sendable 检查

  1. delegate 调用从 nonisolated → actor 的转换
swift 复制代码
nonisolated func urlSession(...) {
    Task { await self.handle(...) }   // ⬅ 跨隔离
}
  1. final class UploadContext: @unchecked Sendable 被存入 actor 状态
swift 复制代码
actor APIService {
    var uploadContexts: [Int: UploadContext]  // ⛔ 需要 Sendable
}
  1. continuation 是 Sendable 的,因此关联的上下文也要 Sendable

Continuation` 是 Swift 用来在 async/await 与传统回调之间"桥接"的对象,例如:

swift 复制代码
withCheckedContinuation { continuation in
    someCallbackAPI { result in
        continuation.resume(returning: result)
    }
}

CheckedContinuation)被设计成可以跨 actor 或线程发送,因此它本身遵守 Sendable

即:你可以把 continuation 存起来,之后在别的线程调用 resume()

那为什么"关联的上下文也要 Sendable"?

因为 如果一个类型里保存了 continuation,那这个类型整体也会被认为要跨线程传递

例如:

swift 复制代码
struct Wrapper {
    let continuation: CheckedContinuation<Int, Never>
}

由于 continuation 是 Sendable,编译器认为 Wrapper 也需要是 Sendable

但如果 Wrapper 里包含了一个非 Sendable 的东西,例如:

它不是一个纯正的美女,它具有女性的外表却还有男性的能力(Wow,Thai ladyboys....)

swift 复制代码
class NotSendable {}

struct Wrapper {
    let continuation: CheckedContinuation<Int, Never>
    let obj: NotSendable(class)
}

这样编译器会报错:

csharp 复制代码
❌ Wrapper is not Sendable because it stores a Sendable continuation with a non-Sendable value.

那么问题来了:Wrapper 是 class ??

swift 复制代码
class Wrapper {
    let continuation: CheckedContinuation<Int, Never>
}

如果你想在 Actor 内部存储它或作为参数传给 Actor,Swift 会报错

swift 复制代码
actor MyActor {
    var wrapper: Wrapper?   // ❌ 编译报错: Wrapper is not Sendable
}

正确姿势:(其实我们一般都喜欢更为混乱的姿势,666)

final class Wrapper: @unchecked Sendable {
    let continuation: CheckedContinuation<Int, Never>
}

Therefore, correct posture is very important, including your's physical actions together, as it determines whether you achieve synchronized orgasms.

So... You know what I mean.

相关推荐
大白的编程笔记1 小时前
大语言模型(Large Language Model, LLM)系统详解
人工智能·语言模型·自然语言处理
凋零蓝玫瑰2 小时前
几何:数学世界的空间密码
人工智能·算法·机器学习
小程故事多_802 小时前
基于LangGraph与Neo4j构建智能体级GraphRAG:打造下一代膳食规划助手
人工智能·aigc·neo4j
Bdygsl2 小时前
数字图像处理总结 Day 2 —— 数字化
图像处理·人工智能·计算机视觉
LDG_AGI2 小时前
【推荐系统】深度学习训练框架(九):推荐系统与LLM在Dataset、Tokenizer阶段的异同
人工智能·深度学习·算法·机器学习·推荐算法
智谱开放平台2 小时前
让 AI 真正懂仓库:如何用 CLAUDE.md 将 Claude Code 的工作效率发挥到极致
人工智能·claude
糯米酒2 小时前
不想使用docker部署n8n的看过来,你可以这样做
人工智能
roman_日积跬步-终至千里2 小时前
【模式识别与机器学习(17)】聚类分析教程【2】:高级方法与离群点分析
人工智能·机器学习·支持向量机