SwfitUI "Sendable" 协议和 "@Sendable" 闭包指示被传递值的公共API是否对编译器线程安全。

22 min read

Swift标准库中的很多类型都已经实现了Sendable接口。对于值类型(例如基础数据类型,数组,字典等)来说,它们天然是线程安全的,因为它们在使用过程中都是复制传值的。因此,它们在标准库中被默认实现为Sendable

例如,Swift中的IntDoubleBoolString等基础类型以及它们组成的ArrayDictionary等集合类型都已经实现了Sendable协议。

此外,对于我们自定义的值类型,例如结构体和枚举,只要它们的所有属性都遵循Sendable协议,那么编译器会自动为我们的类型推导出Sendable的遵循。

让我们来看一个例子:

// 这是一个简单的值类型结构体
struct Article {
    var title: String  // String遵循了Sendable协议
    var authors: [String]  // String遵循了Sendable协议,所以[String]也遵循了Sendable协议
    var readCount: Int  // Int遵循了Sendable协议
}

// Article自动遵循了Sendable协议,因为它的所有属性都遵循了Sendable协议
let article = Article(title: "Title", authors: ["Author1", "Author2"], readCount: 100)

// 因此,我们可以在并发环境中安全地使用Article
asyncDetached {
    print(article.title)
}

Actor 是 Swift 5.5 中引入的新特性,它是一个引用类型,具有自己的内存管理和并发控制机制。Actor 的主要功能是确保并发访问时的线程安全。

Actor 的主要设计原则是“数据隔离”。Actor 内部的状态是私有的,只能通过 Actor 的方法进行访问,并且这些方法在调用时会自动进行调度,确保每次只有一个任务在访问 Actor 的内部状态,避免了数据竞争的问题。

下面是一个简单的 Actor 使用示例:

// 声明一个 Actor 类型
actor Counter {
    private var value = 0  // Actor内部的状态是私有的

    // 提供一个方法用于修改内部状态
    func increment() {
        value += 1
    }

    // 提供一个方法用于获取内部状态
    func get() -> Int {
        return value
    }
}

// 创建一个 Counter 的实例
let counter = Counter()

// 在并发环境中使用 Counter
Task {
    // 调用 Actor 的方法会自动进行调度,确保线程安全
    await counter.increment()
    print(await counter.get())  // 输出:1
}

在这个例子中,我们创建了一个 Counter 类型的 actor,并在并发环境中对它

进行操作。由于 actor 会自动进行调度,我们可以确保 incrementget 方法不会同时被多个任务访问,从而避免了数据竞争的问题。

在Swift中,@Sendable属性和Sendable协议是两个不同但相互关联的概念,它们都是为了支持Swift的并发模型而设计的。

Sendable协议是用于标识一个类型是否可以安全地在并发上下文中传递。例如,Swift的大多数值类型(如IntString等)都是Sendable的,因为它们是不可变的。然而,类类型(引用类型)需要额外的考虑,因为如果一个类的实例被多个线程共享并修改,可能会产生线程不安全的问题。如果你能保证你的类类型是线程安全的,你可以让它遵循Sendable协议。

@Sendable属性用于修饰函数或者闭包,它用来确保函数或闭包内部引用的所有对象都是Sendable的,也就是可以安全地在并发上下文中传递。这对于写并发代码非常重要,因为你不想在你的函数或闭包内部有任何线程不安全的操作。

以下是一个@Sendable属性的使用示例:

actor MyActor {
    var safeArray: [Int] = []

    func appendValue(_ value: Int, completion: @Sendable () -> Void) {
        safeArray.append(value)
        completion()
    }
}

在这个例子中,我们创建了一个actor,它有一个安全的数组safeArray,我们提供了一个appendValue(_:completion:)函数,它接受一个整数值和一个@Sendable的闭包作为参数。因为这个闭包被标记为@Sendable,所以我们可以保证在闭包中没有任何线程不安全的操作。

总的来说,Sendable协议和@Sendable属性都是Swift并发模型的一部分,它们帮助开发者编写更安全的并发代码。