美文网首页Alamofire 源码解析
Alamofire 浅析 <四> retry细节

Alamofire 浅析 <四> retry细节

作者: 狼性刀锋 | 来源:发表于2018-09-07 15:29 被阅读44次

怎么 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

  1. request 失败的时候,可能会触发retry, 准确的说触发AdaptError时候,会触发retry
  2. 触发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回调,多个回调显然是不符合逻辑的

相关文章

网友评论

    本文标题:Alamofire 浅析 <四> retry细节

    本文链接:https://www.haomeiwen.com/subject/elucgftx.html