使用 Combine 发布 API 请求时遇到问题

Posted

技术标签:

【中文标题】使用 Combine 发布 API 请求时遇到问题【英文标题】:Trouble posting API request with Combine 【发布时间】:2020-12-16 16:28:50 【问题描述】:

我是 Combine 游戏的新手,我正在尝试弄清楚如何概括 HTTP POST 请求。

我创建了以下APIService 类来扩展各个资源服务:

import Foundation
import Combine

class APIService 
  let decoder: JSONDecoder
  let session: URLSession
  
  init(session: URLSession = URLSession.shared, decoder: JSONDecoder = JSONDecoder()) 
    self.decoder = decoder
    self.session = session
  


// MARK: JSON API
extension APIService 
  struct Response<T> 
    let response: HTTPURLResponse
    let value: T
  
  
  func post<T: Codable>(
    payload: T,
    url: URL
  ) -> AnyPublisher<Response<T>, APIError> 
    return Just(payload)
      .setFailureType(to: APIError.self) // <<< THIS WAS MISSING!
      .encode(encoder: JSONEncoder())
      .flatMap( [weak self] payload -> AnyPublisher<Data, Error> in
        guard let self = self else 
          return Fail(error: .default("Failing to establish self.")).eraseToAnyPublisher()
        
        var request = URLRequest(url: url)
        request.httpMethod = Methods.post
        request.setValue(
          Mimetypes.json,
          forHTTPHeaderField: Headers.contentType
        )
        request.httpBody = payload
        return self.session
          .dataTaskPublisher(
            for: request
          )
          .tryMap  response -> Response<T> in
            let value = try self.decoder.decode(T.self, from: response.data)
            return Response(
              value: value,
              response: response.response
            )
          
          .mapError  error in
            return APIError.default(error.localizedDescription)
          
          .receive(on: DispatchQueue.main)
          .eraseToAnyPublisher()
      )
      .eraseToAnyPublisher()
  

但是,该类无法编译,并在 post 函数处出现以下错误。

Type of expression is ambiguous without more context

作为 Swift 的新手,尤其是 Combine 的新手,不幸的是,我对如何进行操作一无所知。

非常感谢任何帮助!

自己想出来:解决方案

Failure 类型添加到Just,因此Failure 类型的输入和输出到flatMap 是相等的。或者换一种说法:flatMap 无法将失败的 Never 转换为 PublisherFailure

在我的情况下缺少的行:

Just(payload)
  .setFailureType(to: APIError.self)

【问题讨论】:

【参考方案1】:

你只是有一些编译时错误,当它们发生在臭名昭著的胡思乱想 flatMap Combine 运算符中时,Swift 类型推理系统无法查明这些错误。

    首先,您在创建Response 对象时使用了错误的参数顺序和URLResponse 的类型。更正为:
return Response(
   response: response.response as! HTTPURLResponse,
   value: value
)
    其次,您的 flatMap 实际上并没有返回 AnyPublisher&lt;Data, Error&gt; - 您在其闭包中指定的返回类型。返回类型为AnyPublisher&lt;Response&lt;T&gt;, APIError&gt;。所以,你可以改变它,但是你会遇到另一个问题,那就是flatMapError 类型必须与其上游相同,目前不是APIError,所以我会建议将mapError 移出flatMap。它看起来像这样:
return Just(payload)
    .encode(encoder: JSONEncoder())
    .flatMap( [weak self] payload -> AnyPublisher<Response<T>, Error> in
        guard let self = self else 
            return Fail(error: APIError.default("...")).eraseToAnyPublisher()
        
        var request = URLRequest(url: url)
        request.httpMethod = "Methods.post"
        request.setValue(
            Mimetypes.json,
            forHTTPHeaderField: Headers.contentType
        )
        request.httpBody = payload
        return self.session
            .dataTaskPublisher(
                for: request
            )
            .tryMap  response -> Response<T> in
                let value = try self.decoder.decode(T.self, from: response.data)
                return Response(
                    response: response.response as! HTTPURLResponse,
                    value: value
                    )
            
            .eraseToAnyPublisher()
            
    )
    .mapError  error in
        return APIError.default(error.localizedDescription)
    
    .receive(on: DispatchQueue.main)
    .eraseToAnyPublisher()

【讨论】:

【参考方案2】:

想通了,感谢this solid guide to debugging Publishers on another question。

Just 需要增加一个Failure,因为flatMap 需要输入和输出流的Failure 相同。

我们可以使用setFailureType(to: &lt;T&gt;) 这样做。我已更新我的问题以反映此解决方案。

【讨论】:

以上是关于使用 Combine 发布 API 请求时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

将 .combine 与 cforest 一起使用时遇到问题

SwiftUI 结合 Publisher targetstruct 来解码数据

对外部 API 执行发布请求时遇到问题

使用 Swift 和 Combine 链接 + 压缩多个网络请求

使用 Swift Combine 不返回的 POST 请求不适用于 AnyPublisher<Void, Error>

使用 FileReader api 上传二进制文件时遇到问题