Swift concurrency 9 — Sendable 协议:跨任务共享数据的安全保障
目录
- 什么是 Sendable?
- 为什么需要 Sendable?
- 值类型默认就是 Sendable?
- 强制类类型 `Sendable`(慎用)
- 编译器如何知道我有没有错传?
- 如何修复 Sendable 报错?
- 泛型类型如何处理 Sendable?
- 总结
随着 Swift 并发(Swift Concurrency)的引入,开发者终于有了一种 类型安全的方式来编写并发代码,而不必再依赖传统的锁与队列模型。在这个系统中,有一个非常关键的协议:
Sendable
。
它是 Swift 并发模型的基石,用于标记哪些类型可以在线程间安全传递,从而避免数据竞争(Data Races)。
什么是 Sendable?
Sendable
是 Swift 中的一个协议,用于声明一个类型可以安全地跨越任务或 actor 的隔离域传递。
换句话说:当你在线程或任务间传递某个值时,只有该值的类型遵循了 Sendable
协议,编译器才会认为这个传递是“安全”的。
为什么需要 Sendable?
Swift 并发通过 Task
和 actor
实现并发任务的隔离。当一个值被从一个隔离域传递到另一个时,就可能产生数据竞争。
例如:
Task {await someActor.update(userInfo)
}
如果 userInfo 是个类实例(引用类型),它可能仍在被另一个任务访问,导致并发访问同一内存。
通过引入 Sendable,Swift 编译器可以在构建期检查这种不安全的共享,有效防止潜在的并发 bug。
值类型默认就是 Sendable?
通常来说:
• struct
和 enum
是值类型,复制时会创建副本,不共享内存;
• 所以它们只要内部成员也是 Sendable
,就自动是 Sendable
的。
示例:
struct UserInfo: Sendable {var name: Stringvar age: Int
}
但也可以不显式声明,Swift 会自动推断(只限非 public 类型)。
class
默认不是 Sendable
引用类型(class)是通过共享引用传递的,这意味着两个任务可能会同时访问同一对象,造成数据竞争。
所以:
class UserInfo {var name: Stringvar age: Int
}
默认 不是 Sendable
,编译器会报错:
🛑 Type ‘UserInfo’ does not conform to the ‘Sendable’ protocol
强制类类型 Sendable
(慎用)
有时候你需要让类类型符合 Sendable
,前提是它只包含不可变状态或者你能保证同步访问,并且实现 Sendable
协议的类不能被继承了,需要用 final
修饰。
final class ImmutableUser: @unchecked Sendable {let name: Stringinit(name: String) {self.name = name}
}
• 这里用了 @unchecked Sendable
,表示你告诉编译器:“我保证它是线程安全的”。
• 慎用! 因为你绕过了编译器的检查。
编译器如何知道我有没有错传?
当你创建一个新的 Task 或跨 actor 传递参数时,编译器会检查所有闭包捕获或参数是否为 Sendable
。
actor DataStore {func save(_ userInfo: UserInfo) async {// userInfo 必须是 Sendable}
}Task {let user = UserInfo(name: "Alice", age: 28)await dataStore.save(user) // ✅ 编译通过
}
如果你传递的是非 Sendable
类型,会收到如下报错:
🛑 Captured variable ‘userInfo’ in a Sendable closure is not Sendable
如何修复 Sendable 报错?
- 如果你传的是
class
,考虑:
• 改用struct
• 或者使用@unchecked Sendable
(⚠️ 高风险) - 如果你传的是闭包:
• 给闭包加上@Sendable
,并保证其捕获的变量都是Sendable
Task.detached(priority: .high) { @Sendable inawait service.doWork()
}
泛型类型如何处理 Sendable?
Swift 支持条件 Sendable
遵循:
struct Box<T>: Sendable where T: Sendable {var value: T
}
也就是说,只要 T 是 Sendable
,Box<T>
也能是 Sendable
。
总结
-
Sendable
是并发系统的类型安全基础。 -
默认情况下:
struct
通常自动Sendable
;class
默认不是,需要小心处理
-
编译器能检查你是否做错了类型传递。
-
最安全的做法是尽量使用不可变值类型。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。