导读:本期聚焦于小伙伴创作的《Swift中Sendable协议和@unchecked Sendable区别是什么?手动线程安全类型如何正确声明?》,敬请观看详情。Sendable 协议在 Swift 并发模型中扮演类型安全边界角色,它要求类型的值可以被安全地从一个并发域传递到另一个并发域。但很多开发者容易把它误认为运行时锁或数据竞争检测器。实际上,Sendable 只是静态约束,编译器会根据类型成员检查是否满足要求。对于所有成员为值类型的结构体和枚举,通常可以自动推导;而包含引用类型或可变状态的类型,编译器会拒绝推导,必须手动声明并承担线程安全责任。此时 @unchecked Sendable 允许开发者绕过编译器的自动检查,但前提是已经通过锁、串行队列或其他同步机制保证了内部状态的线程安全。文章会说明 Sendable 的检查规则、@unchecked Sendable 的适用场景、如何为手动加锁的引用类型补充 Sendable 声明,以及滥用这一注解可能带来的数据竞争隐患。通过实际代码示例,读者可以学会在手动管理线程安全时正确标记类型,同时理解为什么应当尽量使用值类型和 actor 来减少 @unchecked Sendable 的使用。

Swift 引入并发模型之后,Sendable 成为跨并发域传值的重要协议。它本身并不执行同步操作,也不会在运行时检测数据竞争,而是通过编译期静态检查,明确标记出哪些类型的值可以安全地从一个任务或线程传递到另一个任务或线程。理解 Sendable 的检查规则,尤其是 @unchecked Sendable 的用途,是手动管理线程安全时无法回避的问题。开发者需要区分协议约束与运行时保护之间的差异,否则很容易在并发代码中留下难以调试的隐患。

Swift中Sendable协议和@unchecked Sendable区别是什么?手动线程安全类型如何正确声明?

Sendable 协议的作用与编译器检查规则

Sendable 协议没有定义任何方法或属性,它的存在完全是为了给编译器提供类型信息。当一个类型遵循 Sendable 时,编译器会检查该类型的每个存储属性是否也满足 Sendable 约束。对于结构体和枚举,如果所有成员都是 Sendable 的,编译器可以自动推导符合 Sendable;对于类,则必须满足更严格的条件:类必须是 final,并且所有存储属性都是不可变的且类型本身也是 Sendable。这样做是为了防止在并发传递过程中出现共享可变状态导致的竞态条件。

举个例子,一个只包含值类型成员的结构体可以自动获得 Sendable 能力:

struct User: Sendable {
    let name: String
    let age: Int
}

上面的代码中,String 和 Int 都是 Sendable 的,因此 User 会在编译时自动合成 Sendable 实现。如果结构体包含一个引用类型的可变成员,编译器就会报错,因为该引用类型可能被多个并发域同时修改。例如:

final class MutableBox {
    var content: String
    init(content: String) {
        self.content = content
    }
}

struct Wrapper: Sendable {
    let box: MutableBox // 错误:MutableBox 不是 Sendable
}

这里的 Wrapper 无法自动遵循 Sendable,因为 MutableBox 是引用类型且具有可变状态。编译器会拒绝这个声明,要求开发者先保证 MutableBox 的线程安全性。这种静态检查机制能够帮助开发者在编译阶段发现潜在的数据竞争风险,但有时也会对已经通过其他手段保证线程安全的类型产生限制。

@unchecked Sendable 的含义与手动线程安全的边界

@unchecked Sendable 是一种编译器指令,允许开发者显式声明某个类型是 Sendable,而无需编译器进行自动检查。它向编译器和所有使用者传达一个承诺:该类型的值可以安全地在并发域之间传递,即使编译器无法通过类型成员分析来验证这一点。这个注解通常用于那些内部已经通过锁、串行队列或原子操作保证线程安全的引用类型。

使用 @unchecked Sendable 需要非常谨慎,因为它绕过了编译器最核心的安全检查。如果类型内部仍然存在未同步的可变状态,那么标记 @unchecked Sendable 只会掩盖问题,而不会解决数据竞争。例如,下面这个类就是不安全的:

final class UnsafeCounter: @unchecked Sendable {
    var value = 0
    func increment() {
        value += 1
    }
}

虽然编译器允许这段代码通过编译,但多个线程同时调用 increment 方法时,value 的读写操作会发生竞态,最终结果不可预测。因此,@unchecked Sendable 并不意味着自动获得线程安全,它只是在告诉编译器:我已经手动处理了线程安全问题,请信任我。如果实际没有处理,后果由开发者自己承担。

手动管理线程安全时如何声明类型为 Sendable

当确实需要通过锁或其他同步机制来保护一个引用类型的内部状态时,可以按照以下步骤将其声明为 Sendable。首先,确保所有对共享状态的访问都经过同步保护;其次,使用 @unchecked Sendable 标记类型。下面的示例展示了一个使用 NSLock 保护计数器的类:

import Foundation

final class ThreadSafeCounter: @unchecked Sendable {
    private var value = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        value += 1
        lock.unlock()
    }
    
    func getValue() -> Int {
        lock.lock()
        defer { lock.unlock() }
        return value
    }
}

在这个示例中,value 的写入和读取都使用同一个 NSLock 进行保护,因此可以认为 ThreadSafeCounter 是线程安全的。标记 @unchecked Sendable 后,这个类型的实例可以被安全地传递到其他并发域中。使用锁的关键是保证所有访问路径都经过同一个锁,任何遗漏都会破坏线程安全假设。此外,还可以使用 os_unfair_lock 或串行 DispatchQueue 来替代 NSLock,但核心原则不变:先完成同步,再声明 Sendable。

另一种更推荐的方案是使用 actor 来管理隔离状态。actor 类型由编译器自动保证线程安全,并且自动符合 Sendable,不需要手动使用 @unchecked。例如:

actor CounterActor {
    private var value = 0
    
    func increment() {
        value += 1
    }
    
    func getValue() -> Int {
        return value
    }
}

如果代码中可以直接使用 actor,应该优先选择 actor,而不是手动加锁并标记 @unchecked Sendable。手动加锁容易出现死锁、锁粒度错误或遗漏保护路径等问题,而 actor 将这些复杂性交给了编译器和运行时处理。只有在无法使用 actor 的场景下,比如需要与现有同步机制或 C 库交互时,才考虑使用 @unchecked Sendable。

常见误区与最佳实践

第一个常见误区是认为 @unchecked Sendable 会为类型添加任何形式的自动同步。实际上它只是一个静态标注,运行时的行为没有任何变化。如果类型原本就存在数据竞争,加上 @unchecked Sendable 后问题依然存在。第二个误区是认为值类型就一定安全,但如果值类型内部包含引用类型成员,并且该引用类型可变,那么值类型本身也不会自动符合 Sendable。第三个误区是只对部分读写操作加锁,而另外一些访问路径忽略了同步,最终仍然会导致竞态。

最佳实践是优先使用不可变值类型。不可变值类型天然满足 Sendable,编译器可以自动推导,不需要任何额外标注。其次,如果需要共享可变状态,优先考虑 actor。actor 提供了编译器强制的隔离能力,避免了手动同步的负担。最后,如果必须使用手动同步,请封装好同步细节,将所有可变状态私有化,并保证所有公开方法都经过相同的锁或队列。标记 @unchecked Sendable 时,使用注释说明同步策略,方便后续维护者理解和检查。

此外,在代码审查中,任何 @unchecked Sendable 的出现都应该作为一个警示信号。审查者需要确认:该类型内部的每个可变状态是否都受到保护?是否存在逃逸的可变引用?锁的粒度是否合理?是否存在死锁风险?只有在确认这些问题后,@unchecked Sendable 才能被安全接受。否则,应该要求开发人员重构为 actor 或不可变类型。通过严格的使用规范,才能充分发挥 Sendable 协议在并发编程中的保护作用,同时避免 @unchecked Sendable 带来的风险。

Swift_Sendableunchecked_Sendable线程安全修改时间:2026-08-13 04:19:09

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。