怎么 retry
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - parameter manager: The session manager the request was executed on.
/// - parameter request: The request that failed due to the encountered error.
/// - parameter error: The error encountered when executing the request.
/// - parameter completion: The completion closure to be executed when retry decision has been determined.
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
/// A closure executed when the `RequestRetrier` determines whether a `Request` should be retried or not.
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
很简单,实现一个实现RequestRetrier接口的类,赋值给Session Manager,然后根据具体的Request决定是否retry
什么时候会触发 retry
- request 失败的时候,可能会触发retry, 准确的说触发AdaptError时候,会触发retry
- 触发session error 的时候会触发, 即
urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
怎么控制 retry 次数
每次retry会导致request.retryCount 加1,你可以根据retryCount ,决定是否继续retry
源码解析
func retry(_ request: Request) -> Bool {
// 确保task不为空
guard let originalTask = request.originalTask else { return false }
do {
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
if let originalTask = request.task {
delegate[originalTask] = nil // removes the old request to avoid endless growth
}
request.delegate.task = task // resets all task delegate data
request.retryCount += 1
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
task.resume()
return true
} catch {
request.delegate.error = error.underlyingAdaptError ?? error
return false
}
}
private func allowRetrier(_ retrier: RequestRetrier, toRetry request: Request, with error: Error) {
DispatchQueue.utility.async { [weak self] in
guard let strongSelf = self else { return }
retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in
guard let strongSelf = self else { return }
guard shouldRetry else {
if strongSelf.startRequestsImmediately { request.resume() }
return
}
DispatchQueue.utility.after(timeDelay) {
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.retry(request)
if retrySucceeded, let task = request.task {
strongSelf.delegate[task] = request
} else {
if strongSelf.startRequestsImmediately { request.resume() }
}
}
}
}
}
逻辑比较简单,如果允许retry,那就retry,那么retry做了什么?
首先移除oldSessionTask的引用,因为这个task已经失效了
retryCount+1,retryCount作为用户决策是否retry的一个指标
update timeStamp
这里要值得注意点在这一句: strongSelf.delegate[task] = request
通过自定义下标的方法,使得语言更加简洁。
/// Access the task delegate for the specified task in a thread-safe manner.
open subscript(task: URLSessionTask) -> Request? {
get {
lock.lock() ; defer { lock.unlock() }
return requests[task.taskIdentifier]
}
set {
lock.lock() ; defer { lock.unlock() }
requests[task.taskIdentifier] = newValue
}
}
这是一种情况,在adapter是吧的时候retry,还有一种情况在session complete with error的时候retry
顺便讲一下线程安全问题,通常一个接口没有进行线程调度到串行队列的话,我们默认它就是可并发的,因为你没法控制用户会在哪个线程调用接口。所以这里加锁确保对数据的操作是线程安全的。
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
/// Executed after it is determined that the request is not going to be retried
let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
guard let strongSelf = self else { return }
strongSelf.taskDidComplete?(session, task, error)
strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
var userInfo: [String: Any] = [Notification.Key.Task: task]
if let data = (strongSelf[task]?.delegate as? DataTaskDelegate)?.data {
userInfo[Notification.Key.ResponseData] = data
}
NotificationCenter.default.post(
name: Notification.Name.Task.DidComplete,
object: strongSelf,
userInfo: userInfo
)
strongSelf[task] = nil
}
guard let request = self[task], let sessionManager = sessionManager else {
completeTask(session, task, error)
return
}
// Run all validations on the request before checking if an error occurred
request.validations.forEach { $0() }
// Determine whether an error has occurred
var error: Error? = error
if request.delegate.error != nil {
error = request.delegate.error
}
/// If an error occurred and the retrier is set, asynchronously ask the retrier if the request
/// should be retried. Otherwise, complete the task by notifying the task delegate.
if let retrier = retrier, let error = error {
retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
guard shouldRetry else { completeTask(session, task, error) ; return }
DispatchQueue.utility.after(timeDelay) { [weak self] in
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
if retrySucceeded, let task = request.task {
strongSelf[task] = request
return
} else {
completeTask(session, task, error)
}
}
}
} else {
completeTask(session, task, error)
}
}
逻辑分为两个部分: 正常逻辑(completeTask),retry逻辑。 retry的时候,是不会触发正常逻辑的,这样避免多次回调,也就是说一旦触发retry,这个sessionTask所有结果不做算都放弃,重新开始发起请求。 因为一般用户不希望retry的时候触发多个多个response回调,多个回调显然是不符合逻辑的
网友评论