在我的 Swift 5 项目中,我有这个扩展来将
async
函数传递给 Publisher.map
:
import Combine
public extension Publisher {
func asyncMap<T>(
_ asyncFunc: @escaping (Output) async -> T
) -> Publishers.FlatMap<Future<T, Never>, Self> {
flatMap { value in
Future { promise in
Task {
let result = await asyncFunc(value)
promise(.success(result))
}
}
}
}
}
但是,我无法使用 Swift 6 在 Xcode 16.1 beta 中编译它,得到:
“‘() async -> ()’类型的任务隔离值作为强传输参数传递;以后的访问可能会出现竞争”。
是否可以将此扩展迁移到 Swift 6?已经尝试在各处添加
Sendable
和 @Sendable
。
如果需要
asyncMap
才能在 @MainActor
上运行,并且 T
是 Sendable
,那么它就很简单并且可以正常工作:
public extension Publisher {
@MainActor func asyncMap<T: Sendable>(
_ asyncFunc: @escaping (Output) async -> T
) -> Publishers.FlatMap<Future<T, Never>, Self> {
return flatMap { value in
Future { promise in
Task {
let result = await asyncFunc(value)
promise(.success(result))
}
}
}
}
}
如果不是……我不相信在不使用通用逃生舱口的情况下这是可能的(我稍后会展示它)。问题是 Future 没有标记它的
promise
参数是 @Sendable
或 sending
或任何东西。它不承诺不保留它并在某个随机点运行它,这可能会导致竞争。它不会这样做,但它没有承诺。而且我认为,如果苹果不更新Combine,就没有任何方法可以真正解决这个问题(他们似乎已经放弃了这一点)。
总有通用的逃生舱口:
@unchecked Sendable
,再加上标记几乎所有其他东西@Sendable
将使这项工作成功:
struct UncheckedSendable<T>: @unchecked Sendable {
let unwrap: T
init(_ value: T) { unwrap = value}
}
public extension Publisher where Output: Sendable {
func asyncMap<T: Sendable>(
_ asyncFunc: @escaping @Sendable (Output) async -> T
) -> Publishers.FlatMap<Future<T, Never>, Self> {
flatMap { value in
Future { promise in
let promise = UncheckedSendable(promise)
Task {
let result = await asyncFunc(value)
promise.unwrap(.success(result))
}
}
}
}
}
对不起。
根据我们对
Future
的了解,我相信这实际上是安全的。许多 Swift 6 称之为不安全的事情实际上都是不安全的,我们只是忽略它,因为“那不会发生”(旁白:有时会发生)。但我相信我们确实知道这是安全的。苹果只是还没有更新Combine 来标记它。