Swift 中 Sendable 的使用

在 Swift 中,Sendable 是一种协议,用于标记类型是否可以安全地跨线程传递。这是 Swift 并发模型(从 Swift 5.5 引入)的一部分,主要用于确保并发环境中数据传递的安全性。


Sendable 的概念

  • Sendable 表示一个类型的实例可以安全地从一个线程传递到另一个线程。
  • 它是一个 marker protocol(标记协议),即协议本身没有要求实现任何方法或属性。
  • 许多标准库类型(如 IntStringArray)已经默认符合 Sendable

在使用并发功能(如 TaskActor)时,编译器会检查类型是否遵循 Sendable,以确保数据传递的线程安全性。


为什么需要 Sendable

在多线程环境中,某些类型可能会因为并发访问而导致数据竞争或未定义行为。Sendable 提供了一个静态安全机制,帮助开发者提前捕获潜在问题。

例如:

  • 值类型(如 struct)可以跨线程传递,因为它们是不可变的或每个线程有独立的副本。
  • 引用类型(如 class)可能在多线程访问时引发问题,需要特殊处理。

Sendable 的自动符合

值类型

  • 如果一个值类型的所有成员类型都符合 Sendable,该值类型自动符合 Sendable
struct MyStruct: Sendable {
    var value: Int // Int 是 Sendable
}

引用类型

  • 类默认不符合 Sendable,因为它们可能会在并发环境中引发数据竞争。
  • 需要显式声明为 Sendable 并确保其线程安全性。
final class MyClass: Sendable {
    let value: Int // 只读属性是线程安全的
}

自定义类型实现 Sendable

如果类型的线程安全性不能由编译器自动推断,开发者可以通过显式声明遵守 Sendable,并使用 @unchecked 关键字告诉编译器我们确认其是线程安全的。

示例:显式声明 Sendable

final class CustomClass: @unchecked Sendable {
    let value: Int

    init(value: Int) {
        self.value = value
    }
}
  • 使用 @unchecked 表示开发者自己保证线程安全,编译器不再检查。

Sendable 的使用场景

在并发任务中传递数据

当在并发任务中使用共享数据时,Sendable 可确保数据传递是安全的。

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

func fetchUserData() async -> User {
    return User(name: "Alice", age: 25)
}

Task {
    let user = await fetchUserData()
    print("User: \(user)")
}

与 Actor 配合

Actor 的隔离模型依赖于 Sendable,只有符合 Sendable 的类型才能在 Actor 间安全传递。

actor UserManager {
    func updateUser(name: String) -> String {
        return "Updated \(name)"
    }
}

let manager = UserManager()
Task {
    let result = await manager.updateUser(name: "Alice")
    print(result)
}

标准库中符合 Sendable 的类型

以下类型默认符合 Sendable

  • 值类型IntStringArrayDictionarySet 等。
  • 一些引用类型URLUUID 等。

编译器检查

如果在并发代码中使用不符合 Sendable 的类型,编译器会报错。例如:

class NonSendableClass {
    var value = 0
}

func performTask(data: NonSendableClass) async {
    // 编译器报错:NonSendableClass does not conform to Sendable
}

let data = NonSendableClass()
Task {
    await performTask(data: data)
}

注意事项

  1. 引用类型线程安全性:对引用类型使用 @unchecked Sendable 时需确保其内部状态是线程安全的。
  2. 可变属性:在符合 Sendable 的类型中,使用 let 限制属性为不可变。
  3. 静态分析:Swift 编译器在异步和并发环境中会自动检查类型是否符合 Sendable

总结

  • Sendable 是 Swift 并发模型的重要组成部分,确保类型可以安全地在线程间传递。
  • 值类型通常自动符合 Sendable,引用类型需要开发者手动声明或确保线程安全。
  • 使用 Sendable 的检查可以帮助捕获并发中的潜在问题,是编写健壮代码的一个重要工具。