You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))
                }
        }
    }
}

修改说明

  1. 添加泛型约束where Output: ExpressibleByNilLiteral:确保value()方法只对输出为可选类型的Publisher生效
  2. 修改返回类型为Output.Wrapped:直接返回可选类型解包后的非可选值
  3. 保留compactMap逻辑:确保仅接收非nil值并传递给后续的async()方法

修复后,调用$foo.value()会直接返回非可选类型,符合预期:

let one = await $foo.value() // 返回非可选类型

内容的提问来源于stack exchange,提问作者Tometoyou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 03:05:29