Swift 6 要求在异步上下文使用类型必须是 Sendable 的问题
Swift 6 要求在异步上下文使用类型必须是 Sendable 的问题

Swift 6 要求在异步上下文使用类型必须是 Sendable 的问题

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 类型) 保存返回的数据,然后进行处理,保证线程安全。

相关文章

1、Swift Sendable协议

2、Swift并发模型中的Actor

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注