Alamofire之request补充

上一篇 Alamofire之request中部份内容写得不是很清晰,因此在这一篇中进行补充。swift

1、下标法

在调用 Alamofire.request 时,最后会来到 SessionManager.request 以下代码: api

那么小伙伴是否主要到红框中的代码呢?数组

这种语法叫下标法。通常用于数组和字典中,那么 delegate 做为 SessionDelegate 的实例,是如何拥有这种能力的呢?服务器

这实际上是 Swift 语法的一种。若是重写对象的 subscript 方法,就可使用下标法。在 SessionDelegate 中,咱们能够看到以下代码: 网络

因此,能够经过 SessionDelegate[task] 获取到对应的 requestsession

那么为何Alamofire要经过 SessionDelegatetask 获取其 request 呢?闭包

咱们知道,SessionDelegate 是面向开发者的协议集合,其内部实现了全部和URLSession有关的Delegate。可是真正处理任务的是 task 对应 request 的 delegate DownloadTaskDelegateDataTaskDelegateUploadTaskDelegate等来具体处理回调。dom

举个栗子🌰:好比下载成功以后,须要将下载的文件移动到指定的目录,这时会回调 SessionDelegate实现的 func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) 方法。那么必定就是 SessionDelegate 来处理吗?答案是否认的。请看代码:异步

这里很明显,分为了两步post

  • 若是开发者已经给 downloadTaskDidFinishDownloadingToURL 赋值了,那么就回调这个闭包。
  • 若是没有闭包,则经过 self[downloadTask]?.delegate 获取到对应task的delegate,让delegate去处理回调。

经过这种方式,实现 SessionDelegate 的任务分发功能。让处理具体事务的 delegate 去处理对应的事务,避免了其内部逻辑混乱。

2、RequestAdapter

咱们在回到 SessionManager.request 中。

小伙伴看到这个 adapter 是否是又以为有点懵?😳

这个 adapter 能干什么呢?咱们先看看这个属性的是个什么鬼👻? 而后咱们就看到了 open var adapter: RequestAdapter?

并且很奇怪的是也没有初始化,那咱们再看看 RequestAdapter 是什么。

这是一个协议。这个协议能够作什么呢?既然Alamofire没有实现,那么必然须要咱们本身来实现。

咱们来举个栗子🌰: 咱们建立一个类 BOAdapter 并继承自 RequestAdapter 协议。 协议要求必须实现一个 adapt 方法。

class BOAdapter: RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        
        return urlRequest
    }
}
复制代码

adapt 方法传入一个 URLRequest 并返回一个 URLRequest。咱们先不对其作任何处理,直接返回这个 urlRequest,而后再调试一下,这个 urlReqeust 究竟是什么。

咱们该如何使用这个 BOAdapter 呢?由于 adapterSessionManager 的属性,因此咱们能够这样:

let urlBD = "https://www.baidu.com"

// 使用实现的 adapter
Alamofire.SessionManager.default.adapter = BOAdapter()
Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"]).response { (response) in
    print(response)
}
复制代码

运行,调试,能够知道,urlRequest 就是咱们请求发起的 urlRequest。咱们能拿到这个 request,那么咱们就能够作不少事情了,好比:

一、给request添加公共参数

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    
    var request = urlRequest
    
    // 设置参数
    request.setValue("token", forHTTPHeaderField: "token")
    
    return urlRequest
}
复制代码

二、重定向

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    
    // 若是是百度地址开头,则重定向到QQ地址
    if let url = urlRequest.url?.absoluteString, url.hasPrefix("https://www.baidu.com") {
        
        let request = URLRequest(url: URL(string: "https://www.qq.com")!)
        
        return request
    }
    
    return urlRequest
}
复制代码

还有其余的一些用法,这里就不一一举例了。

3、validate

在网络请求返回结果以后,通常咱们还会对请求结果作一次验证。

let urlBD = "https://www.baidu.com"

Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"])
    .response { (response) in
        print(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        
        guard let _ = data else {
            
            return .failure(NSError(domain: "NO Response", code: 10086, userInfo: nil))
        }
        
        guard response.statusCode == 200 else {
            
            return .failure(NSError(domain: "Error", code: response.statusCode, userInfo: nil))
        }
        
        return .success
}
复制代码

由于这一步须要服务器配合,因此请小伙伴自行验证。

4、RequestRetrier

在网络请求结束后,会回调 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 方法。 在这个方法中,咱们会看到下面这段代码:

经过注释,咱们知道这段代码的做用是:若是发生错误并设置了 retrier,则异步询问 retrier 是否应重试请求。不然,经过通知 delegate 来完成任务。

那么这个 retrier 又该如何设置呢?查看这个属性 var retrier: RequestRetrier? 并无初始化,因此,也须要咱们本身来设置。

再查看 RequestRetrier:

这是一个协议。而且须要咱们本身来实现。

举个栗子🌰: 咱们自定义一个 BORetrier 继承自 RequestRetrier协议。协议要求实现 should 方法。

should 方法有四个参数,前三个参数都比较好理解。可是第四个参数是一个闭包,这又是什么呢?咱们查看其源码:

经过注释咱们知道:这个一个闭包,并在 RequestRetrier 决定是否一个 request 应该被重试的时候执行。

说明咱们能够经过这个闭包,控制是否重试当前的请求。

class BORetrier: RequestRetrier {
    
    var count: Int = 0
    
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        
        print("manager = \(manager)")
        print("request = \(request)")
        print("error = \(error)")
        
        // 最多重试3次
        if count < 3 {
            completion(true, 1)
            count += 1
        } else {
            completion(false, 0)
        }
    }
}
复制代码

在咱们自定义的 BORetrier 中,咱们规定,重试次数不超过3次。

咱们能够以下面的代码这样使用 BORetrier

let urlBD = "https://www.xxxxxx.com"

Alamofire.SessionManager.default.retrier = BORetrier()
Alamofire.request(urlBD, method: .get, parameters: ["user": "bo"])
    .response { (response) in
        print(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        
        guard let _ = data else {
            
            return .failure(NSError(domain: "NO Response", code: 10086, userInfo: nil))
        }
        
        guard response.statusCode == 200 else {
            
            return .failure(NSError(domain: "Error", code: response.statusCode, userInfo: nil))
        }
        
        return .success
}
复制代码

运行,由于地址 www.xxxxxx.com 没法访问,因此请求返回错误。可是由于有重试机制,因此会在重试3次后再提示错误。

固然,这里也不必定使用次数限制,小伙伴还可使用其余方式控制是否重试。

5、queue

仍是在 SessionManager.request 方法中,不知道小伙伴们是否有注意到这个地方:

咱们知道通常调用的是 task.resume(),而这里的 request.resume()又是怎么回事呢?咱们一块儿来分析一下。

原来是里面获取了一个 task,再调用 task.resume()

同时咱们还看到这样一句代码:delegate.queue.isSuspended = false。在没有任务时,让delegate的队列 queue 恢复。咦❓这是个什么操做呢?这里的队列有什么做用呢?

咱们先来查看一下源码,看看这个 queue 究竟是什么。

咱们看到这个 queueOperationQueue 的实例。而且在下方 TaskDelegate 的初始化方法咱们能够找到 queue 的初始化。

maxConcurrentOperationCount = 1 代表 queue 是一个串行队列。isSuspended = true 代表这个队列默认是挂起的。

那么这个串行队列到底能作什么呢?这就和网络请求的 Timeline 有关了。

小伙伴都知道,在response 方法中,打印 response 会打印出请求结果,在其中会有这样一段字符串 Timeline: { "Request Start Time": 588156668.544, "Initial Response Time": 588156668.835, "Request Completed Time": 588156668.838, "Serialization Completed Time": 588156668.838, "Latency": 0.291 secs, "Request Duration": 0.294 secs, "Serialization Duration": 0.000 secs, "Total Duration": 0.295 secs } 这就是网络请求的 Timeline,标识出了网络请求的起始时间等时间点。那么,这是如何实现的呢?

一、网络请求发起时间

resume 方法中,会给 startTime 赋值当前时间戳,这就是网络请求的发起时间。

二、网络请求结束时间

在初始化 DataRequest 时,会向 queue 队列中添加一个任务,获取当前的时间戳赋值给 endTime,这就是网络请求结束时间。

可是由于当前队列默认为挂起状态,因此不会执行里面的任务。那么这个任务在什么时候执行呢?

在网络请求完成回调 didCompleteWithError 方法时会恢复 queue 队列。

这时就会执行 queue 队列内的任务,完成 endTime 的赋值。

三、初始化Response时间

在网络请求开始返回数据时,会设置 initialResponseTime 为当前时间戳,这个时间就是初始化Response的时间。

四、序列化结束时间

在调用 response 方法时,会向 queue 队列中添加一个任务。由于当前未使用自定义的序列化方法,因此直接返回请求回来的数据,并返回 self.timeline

由于 timeline 是一个计算属性。

因此在调用 self.timeline 时,会初始化 Timeline 对象,并将当前时间戳做为参数 serializationCompletedTime 的值传递给 Timeline 对象。

这个 serializationCompletedTime 就是序列化结束的时间。同时这个任务也是在队列恢复时执行。

五、请求总时长

totalDuration = serializationCompletedTime - requestStartTime 便是当次网络请求的总时长。

其他时间点则须要小伙伴自行探索咯。^_^

附Timeline时序图


以上则是本篇的补充内容。如有不足之处,请评论指正。

相关文章
相关标签/搜索