Alamofire-Request补充

1,554 阅读10分钟

一、回顾

在前面源码探索中,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