iOS Combine:从 URLSession 的 dataTaskPublisher 获取强类型错误



我正在努力从URLSession的.dataTaskPublisher(for:)发布者中形成一个强类型错误对象(下面示例中的ApiErrorResponse对象(,但找不到线索。在这里,我创建了一个从远程API获取笑话对象的类,然后我按如下方式处理结果和错误(该类可以在Xcode Playgrounds中按原样编译(:

class DadJokes {

struct Joke: Codable {
let id: String
let joke: String
}

enum Error: Swift.Error {
case network
case parsing(apiResponse: ApiErrorResponse)
case unknown(urlResponse: URLResponse)
}

struct ApiErrorResponse: Codable {
let code: Int
let message: String
}

func getJoke(id: String) -> AnyPublisher<Joke, Error> {
let url = URL(string: "https://myJokes.com/(id)")!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = ["Accept": "application/json"]
return URLSession.shared
.dataTaskPublisher(for: request)
.map(.data)
.decode(type: Joke.self, decoder: JSONDecoder())
.mapError { error -> DadJokes.Error in
switch error {
case is DecodingError:
//(1) <--- here I want to get the URLResponse.data from the upstream dataTaskPublisher to decode an object of type ApiErrorResponse (which is returned by the remote API) and pass it to the parsing error case
return .parsing(apiResponse: ApiErrorResponse(code: 1, message: "test"))
case is URLError:
return .network
default:
//(2) <---- here I want to get the URLResponse object that is emitted from the upstream  dataTaskPublisher and pass it to the .unknown error case
// I need the URLResponse to read the underlying error info for debugging purposes
return .unknown(urlResponse: URLResponse())
}
}
.eraseToAnyPublisher()
}
}

我有三个问题,其中两个问题在上面的代码中有注释。第三个问题是:我应该怎么做才能从getJoke函数返回一个从未失败的发布者?即,我需要函数的返回类型为AnyPublisher<Result<Joke, Error>, Never>

首先,我建议对错误枚举进行轻微修改,将URLError添加到.network情况中。否则,您无法记录有关网络级别错误的信息。(但这并没有真正影响答案的其余部分。(

enum Error: Swift.Error {
case network(URLError)
case parsing(apiResponse: ApiErrorResponse)
case unknown(URLResponse)
}

对于核心问题,如果你想要URLResponse,那么你不能通过调用.map(.data)这么快就把它扔掉。你需要保留它,直到你决定是否需要它

您还需要考虑一些情况,例如API的数据没有解码为Joke或ApiErrorResponse。这是可能的,你必须处理它。

由于这变得有点复杂,所以最好留下基本的.decode,直接在自己的.map:中处理这些情况

return URLSession.shared
.dataTaskPublisher(for: request)
.map { (data, response) -> Result<Joke, Error> in
// Either decode a Joke
if let joke = try? JSONDecoder().decode(Joke.self, from: data) {
return .success(joke)
}
// or if that fails, try to decode an error
else if let apiResponse = try? JSONDecoder().decode(ApiErrorResponse.self, from: data) {
return .failure(.parsing(apiResponse: apiResponse))
}
// Wasn't either of those; return the whole response
else {
return .failure(.unknown(response))
}
}
.catch { Just(.failure(.network($0))) } // And also catch errors from dataTaskPublisher
.eraseToAnyPublisher()

这里的关键是将成功值映射到成功结果,然后捕获错误并使其成为result.failure类型的成功。像这样:

func getJoke(id: String) -> AnyPublisher<Result<Joke, Error>, Never> {
let url = URL(string: "https://myJokes.com/(id)")!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = ["Accept": "application/json"]
return URLSession.shared
.dataTaskPublisher(for: request)
.map(.data)
.decode(type: Joke.self, decoder: JSONDecoder())
.map { .success($0) }
.catch { (error) -> AnyPublisher<Result<Joke, Error>, Never> in
switch error {
case is DecodingError:
return Just(.failure(.parsing(apiResponse: ApiErrorResponse(code: 1, message: "test")))).eraseToAnyPublisher()
case is URLError:
return Just(.failure(.network)).eraseToAnyPublisher()
default:
return Just(.failure(.unknown(urlResponse: URLResponse()))).eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}

相关内容

  • 没有找到相关文章

最新更新