在 Swift 中,Sendable 是一个协议,用于标记那些可以安全地在并发环境中共享的类型。当使用 Swift 的并发特性(如 async/await、Task、actor 等)时,编译器需要确保在多线程或并发任务之间共享的对象是线程安全的。Sendable 协议帮助编译器判断哪些类型的实例可以在不同的并发任务之间传递而不引发数据竞争或不一致的状态。
public protocol Sendable { }
Sendable 协议的基本概念
当一个类型遵循 Sendable 协议时,表示该类型的实例可以安全地在并发环境中共享。这是通过确保该类型的所有成员和实例在并发环境下都是线程安全的。默认情况下,只有符合特定规则的类型才能自动遵循 Sendable 协议。
例如:
值类型(如结构体)通常是 Sendable,因为它们的实例是不可变的(在并发环境下不会被修改)。
引用类型(如类)通常不是 Sendable,除非它们被标记为 Sendable 或者其所有成员都遵循 Sendable 协议。
如何遵循 Sendable 协议
如果想确保类型是 Sendable,可以显式遵循 Sendable 协议,并遵守一些规则:
值类型(例如 struct 和 enum)默认符合 Sendable,前提是它们的所有属性都符合 Sendable。
引用类型(例如 class)默认不符合 Sendable,因为它们的实例可能会在多个线程中共享并修改,导致并发问题。可以通过标记类类型为 @Sendable 或通过符合协议的方式来确保它是并发安全的。
标记 Sendable 类型
可以显式地为一个类型或类标记它遵循 Sendable 协议,或通过代码本身的行为推断其是否符合 Sendable。例如:
@Sendable class MyClass {
var value: Int = 0
}
这样标记后,MyClass 就变成了 Sendable 类型,可以在并发环境中被安全共享。
Sendable 和并发
Sendable 协议的一个关键作用是确保类型可以安全地跨任务或线程传递。以下是一些常见的与 Sendable 相关的行为:
值类型(如结构体):Swift 认为大多数值类型在并发中是安全的,因为它们通常是不可变的。只要它们的属性也是 Sendable,它们就会自动符合 Sendable 协议。
引用类型(如类):Swift 对引用类型更加谨慎,通常不会自动认为它们是 Sendable。这就意味着必须手动确保它们是线程安全的。如果类内部包含可变状态或共享资源,必须确保它们能在多线程环境下安全访问。
使用 @Sendable 修饰符:可以使用 @Sendable 来明确告诉编译器某个类型符合并发安全的标准,即使它是引用类型。
例如:
// 定义一个 Sendable 的类
@Sendable class Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
这样,当在并发任务之间传递 Counter 实例时,Swift 允许这样做,并保证 Counter 是线程安全的。
不可变类型的 Sendable
如果一个类型是不可变的(例如,它没有任何可变的状态),它自然符合 Sendable 协议,因为它的状态不会在多个线程之间改变。例如:
struct Point: Sendable {
let x: Int
let y: Int
}
这意味着可以在多个并发任务中安全地共享 Point 的实例,因为它的状态是不可变的。
不符合 Sendable 的类型
某些类型(尤其是引用类型)默认不符合 Sendable 协议。例如,如果有一个类,且这个类的属性或行为没有得到充分的线程同步,它就不被视为 Sendable 类型。
class UnsafeClass {
var value: Int = 0
func increment() {
value += 1
}
}
这个类没有线程同步机制,因此默认它是不可 Sendable 的。如果试图在并发环境中共享它,Swift 会发出警告或错误。
使用 Sendable 时的注意事项
使用 Sendable 时,确保所有成员也符合线程安全的标准。不能直接将一个不符合 Sendable 的对象放入一个 Sendable 类型中。
对于类类型,如果它的内部状态是可变的,需要确保在访问这些状态时提供适当的同步措施,如锁或其他并发控制机制。
总结
Sendable 协议是 Swift 中并发编程的核心机制之一,它确保了类型的实例可以在并发环境中安全共享。遵循 Sendable 协议的类型在多线程和并发任务之间传递时是安全的,避免了数据竞争和状态不一致等问题。
扩展知识
@unchecked Sendable
@unchecked Sendable 是用于那些编译器无法自动验证线程安全性的类型,它允许开发者绕过并发安全性检查,表明开发者明确知道这个类型在并发环境中是安全的,即使编译器无法验证它。这个修饰符用在那些类型可能符合 Sendable 协议,但编译器无法完全推断出它们是否安全的情况。
比如,URLResponse 类可能包含一些不可变的状态,它可能可以在多个并发任务之间安全地共享,但由于 URLResponse 是一个复杂的类,编译器无法通过类型分析得出它是否满足并发安全性。因此,Swift 提供了 @unchecked Sendable 来让开发者绕过编译器的检查。
open class URLResponse : NSObject, NSSecureCoding, NSCopying, @unchecked Sendable {
// URLResponse 类实现
}
@unchecked Sendable:
显式声明类型是并发安全的,但不进行编译器检查。
用于那些编译器无法完全验证并发安全性的类型,通常是引用类型。
开发者明确知道类型是安全的,但编译器无法推断出来(可能是因为类比较复杂,或者存在一些隐含的线程安全保证)。
何时使用 @unchecked Sendable?
当有一个类,它的内部状态可能是不可变的,或者已经通过其他手段保证了线程安全性,但编译器无法完全推断出这一点时,可以使用 @unchecked Sendable 来显式标记它,告诉编译器不要进行检查。例如,URLResponse 类是一个继承自 NSObject 的引用类型,内部可能包含一些复杂的可变状态,编译器无法确认其并发安全性,因此使用 @unchecked Sendable 来表明开发者已经保证它是安全的。
总结来说, @unchecked Sendable 是用来告诉编译器已经知道类型是并发安全的,即使它无法进行检查。