【发布时间】:2020-08-14 11:59:54
【问题描述】:
我的 API 返回这种格式,data 可以包含各种响应。
{
status: // http status
error?: // error handle
data?: // your response data
meta?: // meta data, eg. pagination
debug?: // debuging infos
}
我创建了一个Codable 响应类型,其中包含可选数据的泛型,我们不知道它的类型。
struct MyResponse<T: Codable>: Codable {
let status: Int
let error: String?
let data: T?
let meta: Paging?
let debug: String?
}
我现在正在尝试尽可能简洁编写 API 便利方法。所以我有一个函数可以返回一个通用发布者,我可以将它用于所有这些响应,即预先解析响应并提前捕获任何错误的发布者。
首先,我得到一个处理参数输入(如果有)的dataTaskPublisher。 Endpoint 只是方便String enum 对于我的端点,Method 是类似的。 MyRequest 返回一个 URLRequest 以及一些必要的标头等。
注意我定义参数的方式:params: [String:T]。这是标准 JSON,因此它可以是字符串、数字等。
似乎这个T 是个问题。。
static fileprivate func publisher<T: Encodable>(
_ path: Endpoint,
method: Method,
params: [String:T] = [:]) throws
-> URLSession.DataTaskPublisher
{
let url = API.baseURL.appendingPathComponent(path.rawValue)
var request = API.MyRequest(url: url)
if method == .POST && params.count > 0 {
request.httpMethod = method.rawValue
do {
let data = try JSONEncoder().encode(params)
request.httpBody = data
return URLSession.shared.dataTaskPublisher(for: request)
}
catch let err {
throw MyError.encoding(description: String(describing: err))
}
}
return URLSession.shared.dataTaskPublisher(for: request)
}
接下来,我正在解析响应。
static func myPublisher<T: Encodable, R: Decodable>(
_ path: Endpoint,
method: Method = .GET,
params: [String:T] = [:])
-> AnyPublisher<MyResponse<R>, MyError>
{
do {
return try publisher(path, method: method, params: params)
.map(\.data)
.mapError { MyError.network(description: "\($0)")}
.decode(type: MyResponse<R>.self, decoder: self.agent.decoder)
.mapError { MyError.encoding(description: "\($0)")} //(2)
.tryMap {
if $0.status > 204 {
throw MyError.network(description: "\($0.status): \($0.error!)")
}
else {
return $0 // returns a MyResponse
}
}
.mapError { $0 as! MyError }
//(1)
.eraseToAnyPublisher()
}
catch let err {
return Fail<MyResponse<R>,MyError>(error: err as? MyError ??
MyError.undefined(description: "\(err)"))
.eraseToAnyPublisher()
}
}
现在我可以轻松编写端点方法了。这里有两个例子。
static func documents() -> AnyPublisher<[Document], MyError> {
return myPublisher(.documents)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<[Document], MyError>
}
和
static func user() -> AnyPublisher<User, MyError> {
return myPublisher(.user)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<User, MyError>
}
这一切运作良好。请注意,每次,我都必须指定我的确切返回类型两次。我想我可以忍受。
我应该能够简化这一点,这样我就不必每次都以完全相同的方式重复相同的三个运算符(map、mapError、receive)。
但是当我在上面的位置 //(1) 插入 .map(\.data!) 时,我在位置 //(2) 处收到错误 Generic parameter T could not be inferred.。
这真是令人困惑。为什么 input 参数中的泛型类型在这里起任何作用?这必须与上面对.decode 运算符的调用有关,其中所讨论的泛型称为R,而不是T。
你能解释一下吗?如何在上游重构这些运算符?
【问题讨论】:
-
不能是什么类型,如果你不使用Combine,你将如何处理这个JSON?
-
T 是 json 允许的所有类型,但本质上是
String、Int和Float。我正在按照上面的方式处理它,它工作正常。 -
哦,我明白了。您只是对错误消息感到困惑。组合总是给出编译错误!与 your 通用无关。有关如何成功编辑组合代码的信息,请参阅我的技巧和提示页面:apeth.com/UnderstandingCombine/tricksandtips.html
-
但是我不能让它工作......为什么我不能进一步过滤到
data字段?输入应该是一个MyResponse,它有一个可选的data键路径。完成所有操作(捕获错误,在主队列上接收)后,错误仍然存在。下一步是什么? -
特别注意提供显式返回类型部分。但请阅读全文。按照我的技巧,你会明白的。
标签: swift dictionary generics combine