我有一个网络层,目前使用完成处理程序在操作完成时提供结果。
由于我支持多个iOS版本,因此我在应用程序中扩展了网络层以提供对Combine的支持。我现在想将其扩展为同时支持Async/Await,但我不知道如何以允许我取消请求的方式实现它。
基本实现如下:
protocol HTTPClientTask {
func cancel()
}
protocol HTTPClient {
typealias Result = Swift.Result<(data: Data, response: HTTPURLResponse), Error>
@discardableResult
func dispatch(_ request: URLRequest, completion: @escaping (Result) -> Void) -> HTTPClientTask
}
final class URLSessionHTTPClient: HTTPClient {
private let session: URLSession
init(session: URLSession) {
self.session = session
}
func dispatch(_ request: URLRequest, completion: @escaping (HTTPClient.Result) -> Void) -> HTTPClientTask {
let task = session.dataTask(with: request) { data, response, error in
completion(Result {
if let error = error {
throw error
} else if let data = data, let response = response as? HTTPURLResponse {
return (data, response)
} else {
throw UnexpectedValuesRepresentation()
}
})
}
task.resume()
return URLSessionTaskWrapper(wrapped: task)
}
}
private extension URLSessionHTTPClient {
struct UnexpectedValuesRepresentation: Error {}
struct URLSessionTaskWrapper: HTTPClientTask {
let wrapped: URLSessionTask
func cancel() {
wrapped.cancel()
}
}
}
它非常简单地提供了一个抽象,允许我注入一个 URLSession
实例。
通过返回 HTTPClientTask
,我可以从客户端调用 cancel
并结束请求。
我在客户端应用程序中使用 Combine
扩展它如下:
extension HTTPClient {
typealias Publisher = AnyPublisher<(data: Data, response: HTTPURLResponse), Error>
func dispatchPublisher(for request: URLRequest) -> Publisher {
var task: HTTPClientTask?
return Deferred {
Future { completion in
task = self.dispatch(request, completion: completion)
}
}
.handleEvents(receiveCancel: { task?.cancel() })
.eraseToAnyPublisher()
}
}
如您所见,我现在拥有一个支持取消任务的接口。
然而,使用async/await
时,我不确定应该如何实现这个机制来取消请求。
我的当前尝试是:
extension HTTPClient {
func dispatch(_ request: URLRequest) async -> HTTPClient.Result {
let task = Task { () -> (data: Data, response: HTTPURLResponse) in
return try await withCheckedThrowingContinuation { continuation in
self.dispatch(request) { result in
switch result {
case let .success(values): continuation.resume(returning: values)
case let .failure(error): continuation.resume(throwing: error)
}
}
}
}
do {
let output = try await task.value
return .success(output)
} catch {
return .failure(error)
}
}
}
然而这个方法只提供了async
实现,但我无法取消它。
应该如何处理?