Publisher扩展未返回解包值的问题及修复方案
问题:Publisher扩展未自动解包可选值
我编写了一个Publisher扩展,用于接收发布者、等待非nil值发布后获取其值,并以async/await函数的形式返回。代码如下:
extension Publisher { func value() async throws -> Output { try await self .compactMap { $0 } .eraseToAnyPublisher() .async() } } enum AsyncError: Error { case finishedWithoutValue } extension AnyPublisher { /// Returns the first value of the publisher @discardableResult func async() async throws -> Output { try await withCheckedThrowingContinuation { continuation in var cancellable: AnyCancellable? var finishedWithoutValue = true cancellable = first() .sink { result in switch result { case .finished: if finishedWithoutValue { continuation.resume(throwing: AsyncError.finishedWithoutValue) } case let .failure(error): continuation.resume(throwing: error) } cancellable?.cancel() } receiveValue: { value in finishedWithoutValue = false continuation.resume(with: .success(value)) } } } }
但在可选类型的@Published值上使用该扩展时,返回的仍是可选类型,而非解包后的值。明明扩展里会等待非nil值返回,却没有自动解包。例如,假设foo是可选类型的@Published值:
let one = await $foo.value() // 返回可选类型 let two = await $foo.compactMap { $0 }.value() // 返回非可选类型
原因分析
问题出在value()方法的泛型类型推导上。原扩展中,compactMap { $0 }确实会将Publisher<Output?, Failure>转换为Publisher<Output, Failure>,但你定义的value()方法返回类型是Output——这里的Output是原Publisher的关联类型(也就是可选类型)。Swift的泛型系统会优先使用扩展所在类型的关联类型,而非经过运算符转换后的新类型,导致返回值还是未解包的可选类型。
修复方案
通过泛型约束限制value()方法仅适用于输出为可选类型的Publisher,并明确返回解包后的非可选类型:
extension Publisher where Output: ExpressibleByNilLiteral { func value() async throws -> Output.Wrapped { try await self .compactMap { $0 } .eraseToAnyPublisher() .async() } } enum AsyncError: Error { case finishedWithoutValue } extension AnyPublisher { /// Returns the first value of the publisher @discardableResult func async() async throws -> Output { try await withCheckedThrowingContinuation { continuation in var cancellable: AnyCancellable? var finishedWithoutValue = true cancellable = first() .sink { result in switch result { case .finished: if finishedWithoutValue { continuation.resume(throwing: AsyncError.finishedWithoutValue) } case let .failure(error): continuation.resume(throwing: error) } cancellable?.cancel() } receiveValue: { value in finishedWithoutValue = false continuation.resume(with: .success(value)) } } } }
修改说明
- 添加泛型约束
where Output: ExpressibleByNilLiteral:确保value()方法只对输出为可选类型的Publisher生效 - 修改返回类型为
Output.Wrapped:直接返回可选类型解包后的非可选值 - 保留
compactMap逻辑:确保仅接收非nil值并传递给后续的async()方法
修复后,调用$foo.value()会直接返回非可选类型,符合预期:
let one = await $foo.value() // 返回非可选类型
内容的提问来源于stack exchange,提问作者Tometoyou
相关产品推荐
相关产品推荐

