Combine 系列
这可以通过使用 Combine 的 URLSession.dataTaskPublisher 搭配一系列处理数据的操作符来轻松完成。
最简单的,调用 URLSession 的 dataTaskPublisher,然后在数据到达订阅者之前使用 map 和 decode。
使用此操作的最简单例子可能是:
let myURL = URL(string: "https://postman-echo.com/time/valid?timestamp=2016-10-10")
// checks the validity of a timestamp - this one returns {"valid":true}
// matching the data structure returned from https://postman-echo.com/time/valid
fileprivate struct PostmanEchoTimeStampCheckResponse: Decodable, Hashable { // 1
let valid: Bool
}
let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!) // 2
// the dataTaskPublisher output combination is (data: Data, response: URLResponse)
.map { $0.data } // 3
.decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder()) // 4
let cancellableSink = remoteDataPublisher
.sink(receiveCompletion: { completion in
print(".sink() received the completion", String(describing: completion))
switch completion {
case .finished: // 5
break
case .failure(let anError): // 6
print("received error: ", anError)
}
}, receiveValue: { someValue in // 7
print(".sink() received \(someValue)")
})
dataTaskPublisher
是从 URLSession
实例化的。 你可以配置你自己的 URLSession
,或者使用 shared session
.(data: Data, response: URLResponse)
。 map 操作符用来获取数据并丢弃 URLResponse,只把 Data 沿管道向下传递。receiveValue
闭包。failure
闭包。PostmanEchoTimeStampCheckResponse
的实例。要对 URL 响应中被认为是失败的操作进行更多控制,可以对 dataTaskPublisher
的元组响应使用 tryMap
操作符。 由于 dataTaskPublisher
将响应数据和 URLResponse
都返回到了管道中,你可以立即检查响应,并在需要时抛出自己的错误。
这方面的一个例子可能看起来像:
let myURL = URL(string: "https://postman-echo.com/time/valid?timestamp=2016-10-10")
// checks the validity of a timestamp - this one returns {"valid":true}
// matching the data structure returned from https://postman-echo.com/time/valid
fileprivate struct PostmanEchoTimeStampCheckResponse: Decodable, Hashable {
let valid: Bool
}
enum TestFailureCondition: Error {
case invalidServerResponse
}
let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!)
.tryMap { data, response -> Data in // 1
guard let httpResponse = response as? HTTPURLResponse, // 2
httpResponse.statusCode == 200 else { // 3
throw TestFailureCondition.invalidServerResponse // 4
}
return data // 5
}
.decode(type: PostmanEchoTimeStampCheckResponse.self, decoder: JSONDecoder())
let cancellableSink = remoteDataPublisher
.sink(receiveCompletion: { completion in
print(".sink() received the completion", String(describing: completion))
switch completion {
case .finished:
break
case .failure(let anError):
print("received error: ", anError)
}
}, receiveValue: { someValue in
print(".sink() received \(someValue)")
})
在 上个模式 中使用了 map 操作符, 这里我们使用 tryMap,这使我们能够根据返回的内容识别并在管道中抛出错误。
(data: Data, response: URLResponse)
,并且在这里定义仅返回管道中的 Data 类型。tryMap
的闭包内,我们将响应转换为 HTTPURLResponse
并深入进去,包括查看特定的状态码。HTTPURLResponse.statusCode
是一种 Int
类型,因此你也可以使用 httpResponse.statusCode > 300
等逻辑。invalidServerResponse
。Data
以进行进一步处理。当在管道上触发错误时,不管错误发生在管道中的什么位置,都会发送 .failure
完成回调,并把错误封装在其中。
此模式可以扩展来返回一个发布者,该发布者使用此通用模式可接受并处理任意数量的特定错误。 在许多示例中,我们用默认值替换错误条件。 如果我们想要返回一个发布者的函数,该发布者不会根据失败来选择将发生什么,则同样 tryMap 操作符可以与 mapError 一起使用来转换响应对象以及转换 URLError 错误类型。
enum APIError: Error, LocalizedError { // 1
case unknown, apiError(reason: String), parserError(reason: String), networkError(from: URLError)
var errorDescription: String? {
switch self {
case .unknown:
return "Unknown error"
case .apiError(let reason), .parserError(let reason):
return reason
case .networkError(let from): // 2
return from.localizedDescription
}
}
}
func fetch(url: URL) -> AnyPublisher<Data, APIError> {
let request = URLRequest(url: url)
return URLSession.DataTaskPublisher(request: request, session: .shared) // 3
.tryMap { data, response in // 4
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.unknown
}
if (httpResponse.statusCode == 401) {
throw APIError.apiError(reason: "Unauthorized");
}
if (httpResponse.statusCode == 403) {
throw APIError.apiError(reason: "Resource forbidden");
}
if (httpResponse.statusCode == 404) {
throw APIError.apiError(reason: "Resource not found");
}
if (405..<500 ~= httpResponse.statusCode) {
throw APIError.apiError(reason: "client error");
}
if (500..<600 ~= httpResponse.statusCode) {
throw APIError.apiError(reason: "server error");
}
return data
}
.mapError { error in // 5
// if it's our kind of error already, we can return it directly
if let error = error as? APIError {
return error
}
// if it is a TestExampleError, convert it into our new error type
if error is TestExampleError {
return APIError.parserError(reason: "Our example error")
}
// if it is a URLError, we can convert it into our more general error kind
if let urlerror = error as? URLError {
return APIError.networkError(from: urlerror)
}
// if all else fails, return the unknown error condition
return APIError.unknown
}
.eraseToAnyPublisher() // 6
}
APIError
是一个错误类型的枚举,我们在此示例中使用该枚举来列举可能发生的所有错误。.networkError
是 APIError 的一个特定情况,当 URLSession.dataTaskPublisher 返回错误时我们将把错误转换为该类型。dataTaskPublisher
开始生成此发布者。APIError
。https://heckj.github.io/swiftui-notes/index_zh-CN.html
https://github.com/heckj/swiftui-notes