I implemented a method that returns a Future (a Combine Publisher), which produces a value (or error) from a Task, it works in Swift 5:
func call(api: API) -> AnyPublisher<Data?, Error> {
return Future { promise in
Task {
let response = await AF.request( ... ).serializingData().response
if let error = response.error {
promise(.failure(error))
} else {
promise(.success(response.data))
}
}
}.eraseToAnyPublisher()
}
The above code could not compile in Swift 6 with strict concurrency checking, because promise
is NOT a sendable type:
Capture of 'promise' with non-sendable type '(Result<Data?, any Error>) -> Void' in a `@Sendable` closure
Does this mean with Swift 6, it is no longer possible to have a Combine publisher that produce value from an asynchronous method?
I checked Apple's documentation Using Combine for Your App’s Asynchronous Code, but it does not mention how to mix Combine and Task with strict concurrency checking.
I believe the lack of a @Sendable
annotation on Future.Promise
is a bug, as I believe it is supposed to be thread-safe. You could file a feedback report with Apple.
For now, you can work around it using the Swift 6 nonisolated(unsafe)
syntax to tell the compiler not to worry about it:
func call() -> AnyPublisher<Data?, Error> {
return Future { promise in
// Copy promise to a local property to make it nonisolated(unsafe):
nonisolated(unsafe) let promise = promise
Task {
// Now capture promise in a Sendable closure.
promise(.success(Data()))
}
}.eraseToAnyPublisher()
}