Alamofire-Request知识补充

一、请求适配器-RequestAdapter

目的是为了处理Request添加装饰,一个典型的例子是为每一个请求添加token请求,或者对Request重定向。

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

RequestAdapter是一个协议,我们需要自己来实现这个协议。

class JNAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        // token
        //  request 处理
        var request = urlRequest
        request.setValue("xxxxxx", forHTTPHeaderField: "token")

 //request 重定向
        let newUrlRequest = URLRequest.init(url: URL(string: "http://www.douban.com/j/app/radio/channels")!)
        return newUrlRequest
    }
}

1.定义JNAdapter遵循RequestAdapter协议
2.实现adapt,实现自己需要处理的逻辑
3.SessionManager.default.adapter = JNAdapter(),指定adapter

二、自定义验证-validate

网络请求返回的状态码一般是200的段是属于请求成功,但实际开发中,结合业务和后台接口的情况,需要自定义验证逻辑。

SessionManager.default.request(urlStr, method: .get, parameters: ["username":"Jensen","password":"123456"])
    .response { (response) in
        debugPrint(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        guard let _ = data else{
            return .failure(NSError.init(domain: "error", code: 100, userInfo: nil))
        }
        let code = response.statusCode
        if code == 404 {
            return .failure(NSError.init(domain: "error", code:101, userInfo: nil))
        }
        return .success
}

三、RequestRetrier-请求重试器

目的是控制请求的重试机制,一个典型的例子是当某个特殊的请求失败后,是否重试。

 open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    
        //......省略
   
        // 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)
        }
    }
}

SessionDelegate完成请求,如果发生错误,会来到if let retrier = retrier, let error = error,判断Retrier重试闭包是否存在。
retrier 也是继承协议的处理方式,实现参考 adapter


class JNRetrier: RequestRetrier{
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
       
        completion(true,1)//重试
        
        completion(false,0)//不重试
    }
}

1.实现RequestRetrier协议的should方法
2.指定retrierSessionManager.default.retrier = JNRetrier()3.必须要指定条件调用completion(false,0)`停止重试,否则造成死循环.

四、Result

请求返回后,需要序列化后,才是我们最终想要的。

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 序列化处理的结果
result 的结果最终传入到了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实现了CustomStringConvertibleCustomDebugStringConvertible协议 :

extension Result: CustomStringConvertible {
    public var description: String {
       // 就是返回 "SUCCESS" 和 "FAILURE" 的标识
    }
}
extension Result: CustomDebugStringConvertible {
    public var debugDescription: String {
        // 返回标识的同时,还返回了具体内容   
    }
}

可以打印错误详细的信息。

五、Timeline 时间轴

Alamofire提供了Timeline 时间轴,开发者可以通过Timeline快速得到序列化时间,请求时间等,有助于排查问题,优化性能。

timeline: Timeline: {
 "Request Start Time": 588178187.197,
 "Initial Response Time": 588178187.537, 
"Request Completed Time": 588178187.543,
 "Serialization Completed Time": 588178187.543, 
"Latency": 0.340 secs,
 "Request Duration": 0.346 secs, 
"Serialization Duration": 0.000 secs, 
"Total Duration": 0.346 secs }

TaskDelegate的初始化中:

init(task: URLSessionTask?) {
        _task = task

        self.queue = {
            let operationQueue = OperationQueue()

            operationQueue.maxConcurrentOperationCount = 1
            operationQueue.isSuspended = true
            operationQueue.qualityOfService = .utility

            return operationQueue
        }()
    }

1.初始化OperationQueue队列
2.指定队列maxConcurrentOperationCount=1,为串行队列,保证每一个任务顺序执行
3.operationQueue.isSuspended = true队列为挂起状态.

那么这个队列在什么时候开始执行呢?

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

我们在TaskDelegate请求完成的代理方法中看到queue.isSuspended = false,说明是这个时候队列开始执行。

根据timeline记录和计算得出的结果,我们知道肯定会记录请求的开始时间,结束时间等等。顺着这个思路,我们猜测请求的开始时间应该是在task.resume记录的:

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

正如我们所料,startTime = CFAbsoluteTimeGetCurrent(),在这里将请求的开始时间保存在startTime

Request的初始化中:

 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() }
        print(delegate.queue.operationCount)
    }

在操作队列中加入任务{ self.endTime = CFAbsoluteTimeGetCurrent() },队列中加入的第一个任务,当请求结束时,队列resume,将记录结束时间到endTime

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

在请求第一次响应返回数据时,记录时间到initialResponseTime,我们看看timeline是什么时候初始化的?
response中任务到队列中,可以知道是在队列执行到这个任务时,初始化timeline

 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
    }

进入self.timeline,可以看到timeline设置:

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

requestStartTime,requestCompletedTime,initialResponseTime是由之前保存的startTime,endTime,initialResponseTime包装而成。serializationCompletedTime是执行这个闭包的时间。

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
    }

为什么能够对这些时间记录,主要是通过队列同步控制实现的。在一些核心的点保存当前时间! 比如:endTime就是在下载完成那个瞬间让队列执行保存这个时间。
startTime, initialResponseTime 保存在代理中。
如果没有设置值,那么就在当时调用的时候重置当前时间
Timeline 的其他时间就是通过已知的 initialResponseTimerequestStartTimerequestCompletedTimeserializationCompletedTime 计算得出的。

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

推荐阅读更多精彩内容