问题复现
报错代码:
struct Initializer {
init() {
func requestNetworkPermission() async {
// 发送一个轻量的网络请求
guard let url = URL(string: "https://www.fangjunyu.com") else { return }
do {
let (_,_) = try await URLSession.shared.data(from: url)
} catch {
print("网络请求失败:\(error)")
}
}
Task {
await requestNetworkPermission() // 快速的网络请求
}
}
}
这是一个初始化结构,用于初始化应用的相关信息,在下面的函数部分出现了报错信息。
func requestNetworkPermission() async { } // 报错行
报错信息为:
Concurrently-executed local function 'requestNetworkPermission()' must be marked as '@Sendable'; this is an error in Swift 6
Insert '@Sendable '
报错的原因为:requestNetworkPermission在Task外部声明的,但是由于在Task内部调用了它,Swift 6就会要求这个函数符合@Sendable要求。
在并发上下文中(如 Task、Task.detached 或者使用 async let 等),如果调用了一个局部函数,该函数必须显式地被标记为 @Sendable。
因为 Task 可以在并发环境中执行,而局部函数可能会捕获周围作用域的变量,所以编译器需要确保它的执行是安全的。
解决方案为:requestNetworkPermission 标记为 @Sendable:
@Sendable func requestNetworkPermission() async { }
扩展知识
@Sendable
@Sendable 是 Swift 5.5 引入的一个属性,用于标记可以在并发上下文中安全执行的闭包或函数。
在 Swift 6 中,这个检查变得更加严格,因此可能会在并发代码中遇到 @Sendable 的要求。
@Sendable 的作用
在 Swift 的并发模型中,@Sendable(以前叫做 @concurrent)表示这个闭包或函数是“可发送”的,这意味着它可以被安全地从一个线程传递到另一个线程执行。Swift 并发模型需要确保闭包在并发环境中执行时,不会捕获不安全的数据(如非线程安全的变量),以避免数据竞争等问题。
具体来说,@Sendable 保证:
1、函数或闭包不会在并发执行时访问非线程安全的数据。
2、在标记为 @Sendable 的闭包内部,任何被捕获的变量必须是线程安全的或者被复制的,以确保安全性。
什么时候需要使用 @Sendable
在 Swift 中使用 Task、Task.detached 或其他并发相关的 API 时,如果一个闭包被传递到这些 API 中,Swift 会要求它是 @Sendable。例如,如果在 Task 中使用一个局部函数,该函数就需要被标记为 @Sendable。
Task {
let result = await someAsyncFunction()
print(result)
}
如果 someAsyncFunction 是一个局部函数,并且捕获了外部的变量,Swift 会希望确保这个局部函数能够安全地在线程间传递执行。这时,可能会遇到 @Sendable 的要求。
如何声明 @Sendable 函数
@Sendable func doSomething() {
// 线程安全的代码
}
@Sendable 的限制
标记为 @Sendable 的函数或闭包有一些限制:
捕获的变量必须是值类型,或者确保是线程安全的。
不能修改被捕获的外部变量(这意味着不能捕获 var 类型的变量)。
不能捕获不可变的引用(例如,class 类型)。
这些限制是为了确保闭包在并发上下文中执行时,不会导致数据竞争。