Alamofire(4)-Request注意点

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

  • 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: 统一返回给用户的数据
都是一些比较简单容易理解的内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容