Sendable 是 Swift 并发系统中的一个协议,表示”这个类型可以安全地在不同线程/任务之间传递”。
可以把Sendable理解为可以安全地在线程间传递的类型,Non-Sendable 在多线程同时访问时可能会出错。
例如:
static func cleanTempFolder() async throws -> Int {
let tempURL = FileManager.default.temporaryDirectory
guard let enumerator = FileManager.default.enumerator(
at: tempURL,
includingPropertiesForKeys: [.fileSizeKey],
options: [.skipsHiddenFiles]
) else {
throw cleanTempFolderEnum.enumeratorError
}
// 提示: Swift 6 严格并发限制
for case let fileURL as URL in enumerator {
...
}
}
在这段代码中,FileManager.DirectoryEnumerator 不支持在异步上下文中迭代,这意味着在当前异步函数中,可能存在数据竞争的问题。
Swift 6 要求所有在异步上下文中使用的类型必须是 Sendable 的,而 DirectoryEnumerator 不是线程安全的。
Xcode会显示 Swift 6 严格并发检查的错误:
Instance method 'makeIterator' is unavailable from asynchronous contexts; this is an error in the Swift 6 language mode; this is an error in the Swift 6 language mode
解决方案:使用Array() 收集FileManager.DirectoryEnumerator的数据,然后再进行遍历。
guard let enumerator = FileManager.default.enumerator(
at: tempURL,
includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]
) else {
throw cleanTempFolderEnum.enumeratorError
}
// 先转换为数组(强制在当前线程完成迭代)
let allItems = Array(enumerator)
for case let fileURL as URL in allItems {
...
}
Sendable和非Sendable类型
自动 Sendable 类型:
// 1. 值类型(struct/enum)且所有属性都是 Sendable
struct User: Sendable {
let name: String // String 是 Sendable
let age: Int // Int 是 Sendable
}
// 2. 基本类型
let number: Int = 42 // Sendable
let text: String = "Hi" // Sendable
let flag: Bool = true // Sendable
// 3. 不可变的引用类型
let url = URL(string: "...") // Sendable(虽然是 class,但设计为线程安全)
不是 Sendable 的类型:
// 1. 有可变状态的 class
class Counter {
var count = 0 // 可变属性,多线程访问会出问题
}
// 2. FileManager.DirectoryEnumerator
let enumerator = FileManager.default.enumerator(...)
// 内部有状态(当前遍历位置),不是线程安全的
线程安全
在 Swift 中,可能存在多个线程同时访问同一个数据的情况,
例如:
class BankAccount {
var balance = 100
func withdraw(amount: Int) {
// 多线程同时执行会出问题
if balance >= amount {
// 线程A检查通过,准备扣款
// 此时线程B也检查通过
balance -= amount // 两个线程都扣款,余额错误
}
}
}
// 两个线程同时取钱
DispatchQueue.global().async {
account.withdraw(amount: 60) // 线程A
}
DispatchQueue.global().async {
account.withdraw(amount: 60) // 线程B
}
// 结果:余额可能变成 -20
使用actor可以保证线程安全:
actor BankAccount { // actor 自动保证线程安全
var balance = 100
func withdraw(amount: Int) {
if balance >= amount {
balance -= amount // 同一时间只有一个线程能执行
}
}
}
使用 Sendable 类型可以保证线程安全,多个线程同时访问时,不会发生同时访问的情况。
总结
Swift 6 在编译时,会严格检查这些问题,当在 async 异步方法中调用非 Sendable 类型时,就会强制检查并发安全,Xcode显示相关错误提示。
在前面的示例代码中,对于 FileManager.default.enumerator 返回非 Sendable 类型的情况,则使用 Array(Sendable 类型) 保存返回的数据,然后进行处理,保证线程安全。
