使用 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
转换为 Publisher
和 Failure
。
在我的情况下缺少的行:
Just(payload)
.setFailureType(to: APIError.self)
【问题讨论】:
【参考方案1】:你只是有一些编译时错误,当它们发生在臭名昭著的胡思乱想 flatMap
Combine 运算符中时,Swift 类型推理系统无法查明这些错误。
-
首先,您在创建
Response
对象时使用了错误的参数顺序和URLResponse
的类型。更正为:
return Response(
response: response.response as! HTTPURLResponse,
value: value
)
-
其次,您的
flatMap
实际上并没有返回 AnyPublisher<Data, Error>
- 您在其闭包中指定的返回类型。返回类型为AnyPublisher<Response<T>, APIError>
。所以,你可以改变它,但是你会遇到另一个问题,那就是flatMap
的Error
类型必须与其上游相同,目前不是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: <T>)
这样做。我已更新我的问题以反映此解决方案。
【讨论】:
以上是关于使用 Combine 发布 API 请求时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章
将 .combine 与 cforest 一起使用时遇到问题
SwiftUI 结合 Publisher targetstruct 来解码数据
使用 Swift 和 Combine 链接 + 压缩多个网络请求