Swift 合并递归重试
Swift combine recursive retry
我想在服务器响应特定消息(在示例中为 401 错误)时使用 Swift Combine 执行递归一次重试。该响应中的数据改变了模型,允许单次重试。
我为 iOS 13
之前使用的结果类型写了一个小扩展
extension URLSession {
typealias HTTPResponse = (response: HTTPURLResponse, data: Data)
typealias DataTaskResult = ((Result<HTTPResponse, Error>) -> Void)
func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTask {
self.dataTask(with: request) { (data, response, error) in
if let error = error {
completionHandler(.failure(error))
}
completionHandler(.success((response as! HTTPURLResponse, data!)))
}
}
}
我使用此扩展程序执行以下操作
class Account {
enum CommunicationError: Swift.Error {
case counterOutOfSync
}
var counter: Int = 0
func send(isRetry: Bool = false, completionBlock: @escaping URLSession.DataTaskResult) {
var request = URLRequest(url: URL(string: "https://myserver.com/fetch/")!)
request.setValue("\(counter)", forHTTPHeaderField: "MESSAGE-COUNTER")
request.httpMethod = "POST"
URLSession.shared.dataTask(with: request) { [weak self] taskResult in
do {
let taskResponse = try taskResult.get()
if taskResponse.response.statusCode == 401 {
if isRetry { throw CommunicationError.counterOutOfSync }
// Counter is resynced based on taskResponse.data
self?.send(isRetry: true, completionBlock: completionBlock)
} else {
completionBlock(.success(taskResponse))
}
} catch {
completionBlock(.failure(error))
}
}.resume()
}
}
可以看到函数中的递归调用。我想对 Combine 做同样的事情,但我不知道该怎么做。据我所知
func combine(isRetry: Bool = false) -> AnyPublisher<Data, Error> {
var request = URLRequest(url: URL(string: "https://myserver.com/fetch/")!)
request.setValue("\(counter)", forHTTPHeaderField: "MESSAGE-COUNTER")
request.httpMethod = "POST"
return URLSession.shared.dataTaskPublisher(for: request).tryMap {
let response = [=13=].response as! HTTPURLResponse
if response.statusCode == 401 {
if isRetry { throw CommunicationError.counterOutOfSync }
// Counter is resynced based on [=13=].data
return self.combine(isRetry: true)
} else {
return [=13=].data
}
}.eraseToAnyPublisher()
}
感谢任何帮助
如果您有原版 send(isRetry:completionBlock:)
,您可以使用 Future
将其转换为发布者:
func send() -> AnyPublisher<URLSession.HTTPResponse, Error> {
Future { [weak self] promise in
self?.send(isRetry: false) { result in promise(result) }
}
.eraseToAnyPublisher()
}
或者,Combine 已经有一个 .retry
运算符,所以整个事情可以纯粹在 Combine 中完成:
URLSession.shared.dataTaskPublisher(for: request)
.tryMap { data, response in
let response = response as! HTTPURLResponse
if response.statusCode == 401 {
throw CommunicationError.counterOutOfSync
} else {
return (response: response, data: data)
}
}
.retry(1)
.eraseToAnyPublisher()
每当上游出现 any 错误(不仅仅是 401)时,这将重试一次。您可以尝试更多以仅在某些条件下重试(例如,参见 )
我想在服务器响应特定消息(在示例中为 401 错误)时使用 Swift Combine 执行递归一次重试。该响应中的数据改变了模型,允许单次重试。
我为 iOS 13
之前使用的结果类型写了一个小扩展extension URLSession {
typealias HTTPResponse = (response: HTTPURLResponse, data: Data)
typealias DataTaskResult = ((Result<HTTPResponse, Error>) -> Void)
func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskResult) -> URLSessionDataTask {
self.dataTask(with: request) { (data, response, error) in
if let error = error {
completionHandler(.failure(error))
}
completionHandler(.success((response as! HTTPURLResponse, data!)))
}
}
}
我使用此扩展程序执行以下操作
class Account {
enum CommunicationError: Swift.Error {
case counterOutOfSync
}
var counter: Int = 0
func send(isRetry: Bool = false, completionBlock: @escaping URLSession.DataTaskResult) {
var request = URLRequest(url: URL(string: "https://myserver.com/fetch/")!)
request.setValue("\(counter)", forHTTPHeaderField: "MESSAGE-COUNTER")
request.httpMethod = "POST"
URLSession.shared.dataTask(with: request) { [weak self] taskResult in
do {
let taskResponse = try taskResult.get()
if taskResponse.response.statusCode == 401 {
if isRetry { throw CommunicationError.counterOutOfSync }
// Counter is resynced based on taskResponse.data
self?.send(isRetry: true, completionBlock: completionBlock)
} else {
completionBlock(.success(taskResponse))
}
} catch {
completionBlock(.failure(error))
}
}.resume()
}
}
可以看到函数中的递归调用。我想对 Combine 做同样的事情,但我不知道该怎么做。据我所知
func combine(isRetry: Bool = false) -> AnyPublisher<Data, Error> {
var request = URLRequest(url: URL(string: "https://myserver.com/fetch/")!)
request.setValue("\(counter)", forHTTPHeaderField: "MESSAGE-COUNTER")
request.httpMethod = "POST"
return URLSession.shared.dataTaskPublisher(for: request).tryMap {
let response = [=13=].response as! HTTPURLResponse
if response.statusCode == 401 {
if isRetry { throw CommunicationError.counterOutOfSync }
// Counter is resynced based on [=13=].data
return self.combine(isRetry: true)
} else {
return [=13=].data
}
}.eraseToAnyPublisher()
}
感谢任何帮助
如果您有原版 send(isRetry:completionBlock:)
,您可以使用 Future
将其转换为发布者:
func send() -> AnyPublisher<URLSession.HTTPResponse, Error> {
Future { [weak self] promise in
self?.send(isRetry: false) { result in promise(result) }
}
.eraseToAnyPublisher()
}
或者,Combine 已经有一个 .retry
运算符,所以整个事情可以纯粹在 Combine 中完成:
URLSession.shared.dataTaskPublisher(for: request)
.tryMap { data, response in
let response = response as! HTTPURLResponse
if response.statusCode == 401 {
throw CommunicationError.counterOutOfSync
} else {
return (response: response, data: data)
}
}
.retry(1)
.eraseToAnyPublisher()
每当上游出现 any 错误(不仅仅是 401)时,这将重试一次。您可以尝试更多以仅在某些条件下重试(例如,参见