美文网首页
Alamofire-Request补充

Alamofire-Request补充

作者: yahibo | 来源:发表于2019-08-23 17:53 被阅读0次

    一、回顾

    在前面源码探索中,SessionManager管理RequestSessionDelegate的创建,并通过task绑定RequestSessionDelegate对象;Request负责请求的参数的配置,以及task不同任务的创建,创建连接外部(发送请求对象)和TaskDelegate的方法,通过闭包参数,获取TaskDelegate代理事件的内容;TaskDelegate代理事件是由SessionDelegate通过task移交的。总结图:

    SessionManager.png

    以上处理的目的是对任务做分层处理,使结构清晰。

    二、RequestAdapter-适配器

    Request文件下还存在一个协议RequestAdapter。在Manager中创建调用。如下:

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

    联系上下文,adapter并没有被初始化,怎么回事呢?下面看一下是如何定义的:

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

    一个协议内部定义了一个方法,上面定义可以以某种方式检查并适应URLRequest,实际是告诉我们,根据需要遵循该协议并实现该方法。一脸懵逼,实现它干嘛呢?其实也不难猜测,既然给我们该类型,肯定是方便我们设置参数,如token、device、vision等等这些公共参数,其实可以设置的,那下面就来试试。

    1、添加公共参数

    class MyAdapter: RequestAdapter{
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
                 var request = urlRequest
            request.setValue("hibotoken", forHTTPHeaderField: "token")
            request.setValue("device", forHTTPHeaderField: "iOS")
            request.setValue("vision", forHTTPHeaderField: "1.0.0")
            return request
        }
    }
    

    下面设置adapter并发送一个请求:

    let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list"
    let url = URL.init(string: urlStr)!
    Alamofire.SessionManager.default.adapter = MyAdapter()
    Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
    }
    
    • SessionManager中定义了adapter对象,这里就对其赋值一个实现了adapt方法的子类对象
      这里在请求前在adapt中设置了请求头,那么就运行一下,通过抓包看看公共参数是否添加成功:
    args.png

    添加成功,开发中的参数以后就可以单独使用该方法进行管理了。

    2、重定向

    class redireatAdapter: RequestAdapter{
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            let newURLRequest = URLRequest.init(url: URL.init(string: "http://onapp.yahibo.top/public/?s=api/test")!)
            return newURLRequest
        }
    }
    

    直接修改原请求地址,重定向至其他地址。

    为什么会添加公共参数,或重定向?

    代码追踪,追踪到最终使用位置如下:

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
    func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
        guard let adapter = adapter else { return self }
        return try adapter.adapt(self)
    }
    

    这里调用了该方法,这里判断了adapter是否存在,不存在直接使用前面创建并设置好参数的URLRequest对象,如果存在则adapter调用adapt方法,将当前URLRequest对象传出去加工处理。

    三、validate-自定义验证

    开发中经常会根据不同的状态码来处理,比如开发中需要将某一结果定义为错误请求,在error中来做处理,那么在该框架中我们可以使用validate来重新验证,并定义请求结果。代码如下:

    let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
    let url = URL.init(string: urlStr)!
    Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
    }.validate{ (request, response, data) -> Request.ValidationResult in
        print(response)
        guard let _ = data else {
            return .failure(NSError(domain: "没有数据啊", code: 0, userInfo: nil))
        }
        guard response.statusCode == 200 else {
            return .failure(NSError(domain: "是不是哪弄错了", code: response.statusCode, userInfo: nil))
        }
        return .success
    }
    
    • 通过链式方法调用validate验证方法,根据具体需求添加验证逻辑
    • 返回数据为空,定义为错误信息
    • statusCode != 200认为是错误请求
      通过以上试用,我们对Alamofire又有了更多的了解,无论是监听请求进度还是这种验证均以链式调用为主,方便快捷。

    四、RequestRetrier-重新请求

    很多情况下,如果网络请求失败,我们是有重新请求的需求,那么该框架也提供了这样的方法,请求失败都会调用代理方法:urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)。而框架就在该代理方法中做了如下处理:

    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方法
    • 这里retrier是一个继承自RequestRetrier协议的类对象

    RequestRetrier

    /// A type that determines whether a request should be retried after being executed by the specified session manager
    /// and encountering an error.
    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)
    }
    
    • RequestAdapter一样,需要定义类并实现方法

    创建子类并继承协议,实现协议方法如下:

    class MyRetrier: RequestRetrier{
        var count: Int = 0
        func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
            if count<3 {
                completion(true,2)
                 count += 1
            }else{
                completion(false,2)
            }
        }
    }
    
    • 设置重新请求次数,为3次
    • 调用内部实现的闭包,向内传值,告诉内部重新请求还是,终止请求
    • completion有两个参数shouldRetry为是否请求,timeDelay为延时请求的延时时间,这里设置为2秒
    • 延时请求避免,无效请求

    下面就可以设置一个错误连接发送请求尝试一下:

    let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
    let url = URL.init(string: urlStr)!
    Alamofire.SessionManager.default.retrier = MyRetrier()
    Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {
        (response) in
        switch response.result{
        case .success(let json):
            print("json:\(json)")
            break
        case .failure(let error):
            print("error:\(error)")
            break
        }
        }.validate{ (request, response, data) -> Request.ValidationResult in
            print(response)
            guard let _ = data else {
                return .failure(NSError(domain: "没有数据啊", code: 10086, userInfo: nil))
            }
            if response.statusCode == 404 {
                return .failure(NSError(domain: "密码错误", code: response.statusCode, userInfo: nil))
            }
            return .success
    }
    
    • RequestAdapter使用方法一致,需要配置SessionManager的retrier属性

    五、Response-响应结果

    Alamofire对请求到的数据进行了处理再返回给我们,以上请求我们都调用了responseJSON方法来获取最终数据,下面看一下responseJSON内部做了哪些处理:

    public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }
    

    联系上文可知responseJSONDataRequest的一个扩展方法,继承自Request类,因此可以进行链式调用。该方法内部继续调用了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
    }
    
    • 方法内部对请求结果进行了序列化处理
    • 将序列化的结果封装至DataResponse对象中

    以上其实并没有看到我们熟悉的序列化,再继续搜索,找到如下代码:

    public static func serializeResponseJSON(
            options: JSONSerialization.ReadingOptions,
            response: HTTPURLResponse?,
            data: Data?,
            error: Error?)
            -> Result<Any>
        {
            guard error == nil else { return .failure(error!) }
            if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
            guard let validData = data, validData.count > 0 else {
                return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
            }
            do {
                let json = try JSONSerialization.jsonObject(with: validData, options: options)
                return .success(json)
            } catch {
                return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
            }
        }
    
    • 序列化结果封装至Result对象中
    • Result对象最终封装至DataResponse对象中来管理

    从上面代码能够发现response对象管理了请求过程中所有参数:

    var dataResponse = DataResponse<T.SerializedObject>(
        request: self.request,
        response: self.response,
        data: self.delegate.data,
        result: result,
        timeline: self.timeline
    )
    

    因此在请求结果中,我们能够很方便的拿到所有我们需要的信息。

    六、Timeline-时间轴

    为什么有时间轴,在网络请求中,我们需要准确的知道请求耗时,以便于前端或后台做优化处理。下面就看一下Alamofire的时间轴是如何设计的。

    首先我们能够看到,任务是在队列中执行的:

    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
        do {
            let urlRequest = try self.urlRequest.adapt(using: adapter)
            return queue.sync { session.dataTask(with: urlRequest) }
        } catch {
            throw AdaptError(error: error)
        }
    }
    

    队列是在SessionManager中创建,Manager真是什么都管啊。代码如下:

    let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
    
    • 设置标识绑定了当前设备的UUID
    • 该队列是管理发起的任务,和时间轴没有关系

    紧接着初始化TaskDelegate对象。如下:

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

    通过.data(originalTask, task)传入任务task,来初始化TaskDelegate对象如下:

    self.queue = {
        let operationQueue = OperationQueue()
        operationQueue.maxConcurrentOperationCount = 1
        operationQueue.isSuspended = true
        operationQueue.qualityOfService = .utility
        return operationQueue
    }()
    
    • 设置最大并发量为1,让任务顺序执行
    • 初始化的队列默认为挂起状态,因为任务还没有开启

    1、startTime-记录发起请求时间

    任务的创建与执行在Request中进行,代码如下:

    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]
        )
    }
    
    • resumeSessionManager中调用执行
    • 判断任务是否存在如果存在继续执行,因为有任务会被挂起,这里重新启动
    • task不存在,说明任务已结束,队列启动执行其他任务
    • 启动任务前记录请求初始时间,因为有挂起情况,这里对startTime做了判空操作

    2、endTimer-记录请求结束时间

    init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
        self.session = session
        switch requestTask {
        case .data(let originalTask, let task):
            taskDelegate = DataTaskDelegate(task: task)
            self.originalTask = originalTask
        case .download(let originalTask, let task):
            taskDelegate = DownloadTaskDelegate(task: task)
            self.originalTask = originalTask
        case .upload(let originalTask, let task):
            taskDelegate = UploadTaskDelegate(task: task)
            self.originalTask = originalTask
        case .stream(let originalTask, let task):
            taskDelegate = TaskDelegate(task: task)
            self.originalTask = originalTask
        }
        delegate.error = error
        delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
    }
    
    • 创建并分类任务代理,以便任务下发
    • 记录任务结束时间

    上面代码做了一个初始化,为什么说是结束时间呢,因为队列为同步队列,上次请求任务结束后才会执行。即请求完成后,代码如下:

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let taskDidCompleteWithError = taskDidCompleteWithError {
            taskDidCompleteWithError(session, task, error)
        } else {
            if let error = error {
                if self.error == nil { self.error = error }
                if
                    let downloadDelegate = self as? DownloadTaskDelegate,
                    let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
                {
                    downloadDelegate.resumeData = resumeData
                }
            }
            queue.isSuspended = false
        }
    }
    
    • queue.isSuspended = false恢复队列,恢复后上面提到的记录时间任务即可加入到队列中执行

    3、initialResponseTime-初始化响应时间

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
    }
    
    • 初始化数据响应时间,不同任务对应的都有初始化方法,如下载任务,上传任务

    4、TimeLine-时间轴设置

    在响应初始化中,初始化时间轴:

    extension DataRequest {
        @discardableResult
        public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
            delegate.queue.addOperation {
                (queue ?? DispatchQueue.main).async {
                    var dataResponse = DefaultDataResponse(
                        request: self.request,
                        response: self.response,
                        data: self.delegate.data,
                        error: self.delegate.error,
                        timeline: self.timeline
                    )
                    dataResponse.add(self.delegate.metrics)
                    completionHandler(dataResponse)
                }
            }
            return self
        }
    }
    
    • 时间轴是要面向开发的,因此在响应初始化时,被封装至Response

    初始化时间轴,对前面的时间记录做统一管理:

    extension Request {
        var timeline: Timeline {
            let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
            let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
            let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
            return Timeline(
                requestStartTime: requestStartTime,
                initialResponseTime: initialResponseTime,
                requestCompletedTime: requestCompletedTime,
                serializationCompletedTime: CFAbsoluteTimeGetCurrent()
            )
        }
    }
    

    时间轴初始化,计算请求间隔,序列化时间间隔:

    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记录了请求过程中的操作时间点,并计算了每部操作的时间间隔,在请求结束后封装至Response中。这里通过队列来同步请求中的操作,以保证startTime、endTime的准确性,其他时间记录是在请求代理回调中设置。

    TimeLine:

    timeline.png

    相关文章

      网友评论

          本文标题:Alamofire-Request补充

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