美文网首页
Alamofire(4)-Request注意点

Alamofire(4)-Request注意点

作者: BoxJing | 来源:发表于2019-08-23 16:07 被阅读0次

    本篇主要介绍的主要内容:

    • RequestAdapter
    • Validate
    • RequestRetrier
    • TaskDelegate内部queue
    • Timeline
    • Result
    1. Adapter

    首先再回顾一下request方法的源码:

    open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
            var originalRequest: URLRequest?
    
            do {
                originalRequest = try urlRequest.asURLRequest()
                let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
    
                let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
                let request = DataRequest(session: session, requestTask: .data(originalTask, task))
    
                delegate[task] = request
    
                if startRequestsImmediately { request.resume() }
    
                return request
            } catch {
                return request(originalRequest, failedWith: error)
            }
        }
    

    在task的创建里面传了一个adapter,看名字意思就是一个适配器,类型是一个RequestAdapter,看到源码:

    /// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
    public protocol RequestAdapter {
        /// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
        ///
        /// - parameter urlRequest: The URL request to adapt.
        ///
        /// - throws: An `Error` if the adaptation encounters an error.
        ///
        /// - returns: The adapted `URLRequest`.
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest
    }
    

    明显就是一个协议!找到最终adapter使用的地方:

    func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
            guard let adapter = adapter else { return self }
            return try adapter.adapt(self)
        }
    

    发现就是一个尝试调用协议方法adapt,看到这里基本上就知道我们要干什么了,我们要想看看它到底干了什么,就需要实现一下func adapt(_ urlRequest: URLRequest) throws -> URLRequest方法,adapter对象通过源码open var adapter: RequestAdapter?可以知道是SessionManager的一个属性,我们需要给它赋值,然后实现协议方法,尝试一下,的确走进去了:

    SessionManager.default.adapter=BoxAdapter()
    SessionManager.default.request(urlStr)
        .response { (response) in
            print(response)
    }
    
    class BoxAdapter:RequestAdapter {
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            print("走进了adapt方法:\(urlRequest)")
            return urlRequest
        }
    }
    

    那么这个协议能干嘛用?想必大家开发的过程中都遇到过后端要求咱们客户端来自定一些请求头信息,有木有???有的人就每次都去写那点设置请求头的代码,有的人吶写个公用方法,每次调用一下公用方法,那么用Alamofire的话,就可以直接在这个协议里做了,来个实实在在的例子:

    class BoxAdapter:RequestAdapter {
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            debugPrint("走进了adapt方法:\(urlRequest)")
            var boxRequest = urlRequest
            boxRequest.setValue("BoxJing", forHTTPHeaderField: "BoxSign")
            return boxRequest
        }
    }
    

    抓包前后的对比:


    自定义请求头前.png 自定义请求头后.png

    这个功能使用这个协议完全没毛病,这里还可以用在请求重定向等方面,同志们自由发挥了。

    2. validate

    顾名思义这个东西是用来验证的,能用来干?既然这么优秀的框架提供了它,想必一定有用处,比如说我们的服务要求只要是返回的状态码在200~300之间的都算成功,或者说不同的状态值返回自定义的错误信息,那么就可以直接在这里干起来,执行的顺序是先validateresponse

    SessionManager.default.request(urlStr)
                .response { (response) in
                    print("response method called")
                }
                .validate { (request, response, data) -> Request.ValidationResult in
                    print("validate method called")
                    guard let _ = data else {
                        return .failure(NSError.init(domain: "BoxJing", code: 50500, userInfo: nil))
                    }
                    if response.statusCode == 404 {
                        return .failure(NSError.init(domain: "BoxJing", code: 50404, userInfo: nil))
                    }
                    return .success
            }
    
    3. RequestRetrier

    这个东西看名字就知道是重试,只要重试那么一定是发生在出现了错误,直接去SessionDelegate里找一个有错误的代理回调,不出意外的话,里面一定会出现这个东西:

    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)
            }
        }
    

    里面的retrier.should(sessionManager, retry: request, with: error)的确出现了RequestRetrier的实例,可以仔细的翻一翻,只要是带了Error的都出现了retrier,非常的合情合理。干进去看一看源码:

    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)
    }
    

    有没有觉得跟Adapter很像,也是一个协议,里面一个方法,直接写代码实现这个协议方法,看看能不能像我们期望的一样走进来:

    class BoxRetrier:RequestRetrier {
        func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
            print("Retrier should method")
        }
    }
    
    SessionManager.default.retrier=BoxRetrier()
            SessionManager.default.request(urlStr)
                .response { (response) in
                    print("response method called")
                }
                .validate { (request, response, data) -> Request.ValidationResult in
                    print("validate method called")
    //  方便测试 直接返回error
                    return .failure(NSError.init(domain: "BoxJing", code: 50500, userInfo: nil))
            }
    

    运行后的确可以顺利的打印出:


    should方法里有一个回调RequestRetryCompletion,点进去看一看源码:

    public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
    

    参数还是非常简单的,shouldRetry:是否需要重试,timeDelay:重试需要的延迟时间,一般情况下重试completion(true,1.0)的间隔时间规则是越来越长,也就是说第一次重试间隔1s,第二次间隔2s,第三次间隔4s,并不是一直无限的重试下去,一般的业务都会设置一个最大的重试次数,达到最大的错误次数后就认为没救了,真正的失败了,不再重试,调用completion(false,0.0)结束重试。如何不停的retry的?重要的一句代码let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false,里面有一个retry方法:

    func retry(_ request: Request) -> Bool {
            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
            }
        }
    

    里面拿到task然后resume,等于是又一次请求走起了。

    4. TaskDelegate内部queue

    开篇的request源码里有一句:if startRequestsImmediately { request.resume() }startRequestsImmediatelySessionManager的一个默认为true的属性,我们外面基本也不会用到手动启动这个请求,所以基本是直接执行request.resume(),task我们都知道是可以resume的,那这个request怎么可以resume?这个resume方法里必定会有一个taskresume

    open func resume() {
            guard let task = task else { delegate.queue.isSuspended = false ; return }
    
            if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
    
            task.resume()
    
            NotificationCenter.default.post(
                name: Notification.Name.Task.DidResume,
                object: self,
                userInfo: [Notification.Key.Task: task]
            )
        }
    

    果不其然,跟我们的猜测一毛一样!的确有一个task.resume(),代码里有一句delegate.queue,这个queue是个什么,看看源码会发现public let queue: OperationQueue,也就是它说是taskDelegate的一个属性,在taskDelegate初始化的时候一起初始化的:

    init(task: URLSessionTask?) {
            _task = task
    
            self.queue = {
                let operationQueue = OperationQueue()
    
                operationQueue.maxConcurrentOperationCount = 1
                operationQueue.isSuspended = true
                operationQueue.qualityOfService = .utility
    
                return operationQueue
            }()
        }
    

    可以看到这个queue初始化的时候是同步的,而且是挂起状态的,类型为.utility的请求类型。这个queue里放的什么东西不得而知,我们直接搜一下queue.addOperation看看工程里哪里往这个队列里放东西了:


    就这几个地方,放的东西自己去源码里看一看,瞧一瞧!这个队列保证了所有的任务可以按顺执行,必须是请求request完才能走response,非常的合理。
    5. Timeline

    一个时间轴的概念,Alamofire提供这个一个时间轴还是很有用的,可以直观的看到某次请求的各个时间段所花费的时间。一起看下Timeline初始化源码:

    public init(
            requestStartTime: CFAbsoluteTime = 0.0,
            initialResponseTime: CFAbsoluteTime = 0.0,
            requestCompletedTime: CFAbsoluteTime = 0.0,
            serializationCompletedTime: CFAbsoluteTime = 0.0)
        {
            self.requestStartTime = requestStartTime
            self.initialResponseTime = initialResponseTime
            self.requestCompletedTime = requestCompletedTime
            self.serializationCompletedTime = serializationCompletedTime
    
            self.latency = initialResponseTime - requestStartTime
            self.requestDuration = requestCompletedTime - requestStartTime
            self.serializationDuration = serializationCompletedTime - requestCompletedTime
            self.totalDuration = serializationCompletedTime - requestStartTime
        }
    

    每个请求都会带着一个自己的Timeline,在不同的阶段,赋值不同的属性值,最后计算出每个阶段的时间值。比如在Request的resume里会有if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }这句代码,意味着会给请求的开始时间赋值,在Request初始化方法里delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() },意味着会给请求初始化完成的时间赋值,其他情况自行查看源码翻一翻。

    6. Result

    Result是经过请求和序列化后返回给用户的数据。只有所有的步骤都成功后才会返回给用户成功,如果某一步失败了,就返回给用户失败,从这点出发,可以大胆的猜测一下,result里面应该会有一个标识来标识成功或者失败,干进源码:

    public enum Result<Value> {
        case success(Value)
        case failure(Error)
    
        /// Returns `true` if the result is a success, `false` otherwise.
        public var isSuccess: Bool {
            switch self {
            case .success:
                return true
            case .failure:
                return false
            }
        }
    
        /// Returns `true` if the result is a failure, `false` otherwise.
        public var isFailure: Bool {
            return !isSuccess
        }
        ...
    }
    

    完全符合我们的逻辑,看下有序列化的Response:

    public func response<T: DataResponseSerializerProtocol>(
            queue: DispatchQueue? = nil,
            responseSerializer: T,
            completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
            -> Self
        {
            delegate.queue.addOperation {
                let result = responseSerializer.serializeResponse(
                    self.request,
                    self.response,
                    self.delegate.data,
                    self.delegate.error
                )
    
                var dataResponse = DataResponse<T.SerializedObject>(
                    request: self.request,
                    response: self.response,
                    data: self.delegate.data,
                    result: result,
                    timeline: self.timeline
                )
    
                dataResponse.add(self.delegate.metrics)
    
                (queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
            }
    
            return self
        }
    

    result会放在dataResponse里返回给用户,这里面会牵扯到了ResponseSerialize,在后面的文章里慢慢的剖析!

    至此,本篇文章介绍了
    - RequestAdapter:自定义请求信息/重定向
    - Validate:自定义错误标准
    - RequestRetrier:自定义重试规则
    - TaskDelegate内部的queue:限制执行顺序
    - Timeline: 统计请求的各个时间段时间
    - Result: 统一返回给用户的数据
    都是一些比较简单容易理解的内容。

    相关文章

      网友评论

          本文标题:Alamofire(4)-Request注意点

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