Alamofire(三) 实战分析

前言

前面分析了一波Request的整个流程和源码,本文是讲解到底在项目中如何实际使用,来满足对于网络请求的各种需求。

Adapter动态适配

 SessionManager.default.adapter = DLAdapter()
        SessionManager.default.request(myGetUrlString, method: .get, parameters: ["array":getJsonFromArray(array)])
            .response { (response) in
                debugPrint(response)
        }

class DLAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var request = urlRequest
        request.setValue("token_value", forHTTPHeaderField: "token");
        return request;
    }
}
  • 实现RequestAdapter,实现adapt方法,在这里面做了设置请求头token的操作
  • SessionManager.default.adapter = DLAdapter(),配置了Adapter后,所有的请求,都会带上个这个请求头的值
  • 还可以实现重定向功能等等,具体想统一对request做什么处理,要看项目的具体需求了
源码探索

探索里面的源码实现,我这里是把几处的源码都综合在一处了

open var adapter: RequestAdapter?
   
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)

let urlRequest = try self.urlRequest.adapt(using: adapter)

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

public protocol RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

在创建task的时候会传递当前的adapter,这个adapter在这里try adapter.adapt(self)会执行,而且这个adapter找不到具体的实现,它是外界直接传递进来的,外界传递了则使用,外界不传递则不使用。

Retrier请求重试

使用方法很简单,和上面基本一样,实现RequestRetrier协议里的should方法即可

SessionManager.default.retrier = DLAdapter()
class DLAdapter: RequestAdapter,RequestRetrier{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var request = urlRequest
        request.setValue("token_value", forHTTPHeaderField: "token");
        return request;
    }
    
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        completion(true,1)
    }
}

  • 可以在should方法里根据条件判断是返回true还是false,true则表示需要重试
  • 在使用的时候只要设置SessionManager.default.retrier即可
  • 实用的地方很多,比如某个接口需要轮询重复查询,这种情况用上retrier很方便
源码探索

找到使用到retrier的方法

  open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: 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,会同步询问request是否需要retrier,没发生错误就通知taskDelegate请求完成。
  • 这里面的方法就相当于一个递归,如果retrySucceeded一直为ture,则会一直调用下去,所以我们要注意在should方法中要有返回为false的情况,不然会一直递归下去

自定义验证validate

在实际相关项目中,我们可能有一些响应码需要特殊处理,这时候自定义验证就很适合使用了,如果能配上上面的retrier更加好用,返回error后再retrier

SessionManager.default.request(myGetUrlString, method: .get, parameters: ["array":getJsonFromArray(array)])
            .response { (response) in
                debugPrint(response)
            }.validate { (request, response, data) -> Request.ValidationResult in
                guard let _ = data else{
                    return .failure(NSError.init(domain: "test", code: 10089, userInfo: nil))
                }
                let code = response.statusCode
                if code == 404 {
                    return .failure(NSError.init(domain: "test2", code: 100800, userInfo: nil))
                }
                return .success
        }    

这里是自定义了两个error,在data为空和statusCode为404的时候都会返回自定义的error信息

    public func validate(_ validation: @escaping Validation) -> Self {
        let validationExecution: () -> Void = { [unowned self] in
            if
                let response = self.response,
                self.delegate.error == nil,
                case let .failure(error) = validation(self.request, response, self.delegate.data)
            {
                self.delegate.error = error
            }
        }

        validations.append(validationExecution)

        return self
    }

这里是把验证的闭包加到了validations里,然后在请求完成后,会把validations里的闭包都执行一遍

    open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
      // Run all validations on the request before checking if an error occurred
        request.validations.forEach { $0() }
    }

TimeLine

Alamofire提供了TimeLine时间线,为了我们开发的便捷,能够通过Timeline快速得到这个请求的时间数据,做一些可能需要做的优化操作

timeline: Timeline: { 
"Request Start Time": 588099247.070,
"Initial Response Time": 588099272.474, 
"Request Completed Time": 588099272.475, 
"Serialization Completed Time": 588099272.475, 
"Latency": 25.404 secs, 
"Request Duration": 25.405 secs, 
"Serialization Duration": 0.000 secs, 
"Total Duration": 25.405 secs 
 }    

请求开始时间

open func resume() {
    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
}

添加请求完成时间记录

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session
       // 省略无关代码,方便阅读
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}

初始化响应时间

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}

时间轴设置

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
}

Result

Alamofire请求完数据后会返回一个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
        )
    }
    return self
}

可以看到这个result是在经过responseSerializer.serializeResponse序列化后返回的,DataResponse

public enum Result<Value> {
    case success(Value)
    case failure(Error)
    
   // 提供成功还有失败的校验
    public var isSuccess: Bool {... }
    public var isFailure: Bool {...}
    public var value: Value? {...}
    public var error: Error? {... }
}

Result的枚举值只有success和failure,使用起来非常方便

总结

以上都是比较实用的Alamofire开发技巧,希望大家能熟练使用!

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

推荐阅读更多精彩内容