六游的博客小站
Alamofire源码解析专栏(1)- 启程
发布于: 2020-08-04 更新于: 2020-10-20 阅读次数: 

Alamofire源码解析专栏文章地址



Alamofire是Swift最流行的网络请求框架,其简易的使用方法和强大的可定制能力使得框架本身备受iOS开发人员的喜爱。我在使用这个框架的时候经常会产生这样的好奇:这个框架到底是通过什么样的方式去管理如此庞大的网络请求系统的?又是如何给每一个请求提供了高定制化的接口的?于是这样的好奇心驱使我给自己开了个新坑,没错,就是这个Alamofire框架源码阅读专栏,不知道自己是否能够坚持下来把所有东西都写完,但是我会尽量努力。本篇是Alamlfire源码阅读专栏的开篇,主要介绍了框架程序的入口,Session类中的成员属性以及一些进一步阅读源码之前需要了解的小组件

程序入口

程序的入口存在于Alamofire.swift文件之中,该文件中只有简简单单的一行代码,将Session类的一个default单例赋值给了AF这个常量变量。

1
public let AF = Session.default

经常使用Alamofire的朋友们看到这一行代码可能就会感觉到自己已经到达了Alamofire宇宙的大门前了。我们在平常使用的过程中,经常使用AF.request这样的代码去发起一个请求,实际上是在调用Session类实例的方法,Session是Alamofire中的一个核心入口类,该类定义在Session.swift文件中。Session这个类在Alamofire中主要负责创建/发起请求、管理请求、响应URLSession的代理方法、管理URLSession、响应/发起Alamofire系统中的各种通知等,从它的职责就可以看到这个类是Alamofire中的核心类,事实也确实是这样,Session类管理的所有请求的整个生命周期,所以我们阅读源码也要围绕这个类去展开,但是不用着急地去死磕这个类的源码,因为Session的源码中包含了许多与外围组件交互的代码,直接去死磕这样一个类是很难理解的。跟随我的脚步,我会一步步带你逐渐深入Alamofire

Session类中的实例变量

这里我们主要先简要介绍一下Session类中的实例变量,只是为了对其有个印象,有些实例变量你可能看不懂是什么意思或者什么作用,不要去纠结,看不懂的直接跳过,只需要对这些属性有个大概的印象,之后用到了这些属性再回来看,你就能很清楚每个属性的作用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* Session类的共享单例对象 */
public static let `default` = Session()

/* 一个Session中持有一个URLSession对象,底层使用这个URLSession发起各种请求 */
public let session: URLSession
/* 处理URLSession的代理方法的一个类,其本身实现了URLSessionDelegate、URLSessionDataDelegate、URLSessionDownloadDelegate等多个原生Session代理协议 */
public let delegate: SessionDelegate
/* rootQueue用来执行所有内部的回调方法和状态更新操作,必须是串行队列(为了保证对资源操作的有序性) */
public let rootQueue: DispatchQueue
/* 下面这个值决定实例是否对所有创建的request自动调用resume(),如果为true则会在request创建完成后自动立马调用resume方法 */
public let startRequestsImmediately: Bool
/* 用来异步创建URLRequest的queue,默认情况直接使用上面的rootQueue,如果创建URLRequest成为了你的系统的瓶颈可以尝试引入另外一个附加的queue */
public let requestQueue: DispatchQueue
/* 用来序列化请求响应数据的queue,默认情况直接使用上面的rootQueue,如果对response的序列化成为了你的系统的瓶颈,可以尝试引入一个附加的queue */
public let serializationQueue: DispatchQueue
/* 全局的请求拦截器,可以在请求发出前或者响应前做出一些判断,并根据需要拦截或者放行请求。 */
public let interceptor: RequestInterceptor?
/* 全局的证书验证器 */
public let serverTrustManager: ServerTrustManager?
/* 全局的请求重定向处理器,当一个请求被系统代理告知需要重定向,而请求又没有自己的一个重定向处理器的时候,就会调用全局的处理器来处理重定向 */
public let redirectHandler: RedirectHandler?
/* 全局的缓存处理器,响应请求所产生的缓存 */
public let cachedResponseHandler: CachedResponseHandler?
/* 事件监听器,是一个集中的事件分发中心 */
public let eventMonitor: CompositeEventMonitor
/* 默认的事件监听器订阅者列表,事件监听器中的所有订阅者由默认的订阅者加初始化传入的监听者组成 */
public let defaultEventMonitors: [EventMonitor] = [AlamofireNotifications()]

/* 内部的map,存储着创建的Request与对应的URLSessionTask之间的映射 */
var requestTaskMap = RequestTaskMap()
/* 一个set,存储这当前活跃的(还未被关闭)的所有request */
var activeRequests: Set<Request> = []
/* */
var waitingCompletions: [URLSessionTask: () -> Void] = [:]

Session类中的入口方法

前面说到我们使用AF.request()这样的方式来发起一个请求,这样做实际上是调用的Session的request实例方法,接下来我们就来看一下这个实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest {
let convertible = RequestConvertible(url: convertible,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers,
requestModifier: requestModifier)

return request(convertible, interceptor: interceptor)
}

可以看到这个方法是非常的简单,但是使用到了许多我们不知道的类型,接下来主要介绍这个方法中出现的一些类型以及这些类型的作用,这也是这篇文章主要分析的地方

URLConvertible

URLConvertible是一个协议类型,其定义了一类可以通过asURL方法转换为系统原生URL的对象,协议定义如下

1
2
3
public protocol URLConvertible {
func asURL() throws -> URL
}

除了定义了这个协议,Alamofire中还对系统原生类做了URLConvertible的扩展。比如:Alamofire将原生的String扩展为URLConvertible类型,这样我们就可以直接使用URL字符串来发起请求,使得从外部调用方法发起一个请求变得十分的简便

1
2
3
4
5
6
7
8
9
10
extension String: URLConvertible {
public func asURL() throws -> URL {
guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
return url
}
}

extension URL: URLConvertible {
public func asURL() throws -> URL { self }
}

这种Convertible的模式是一种值得学习的思想,它可以将一个类型与几个其他相关的类型以一种非常优雅的方式关联起来,即几个其他相关类型可以非常便捷的转换为一个目标类型。Alamofire中还有许多运用到了这种思想的地方,等我们遇到的时候再说

HTTPMethod

这个类型很好理解,就像名字那样,HTTPMethod是用来表示HTTP请求的方法的类型,其本身是一个结构体,只是通过多个静态变量,将请求方法字符串与各个变量对应起来,这样在使用的时候就不容易出错。至于为什么使用了结构体而不是用枚举,我想大概是因为使用结构体更易定制化,如果这里有使用一些比较不常见的方法的需求时,可以直接重新创建一个结构体的实例来表示这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
public static let connect = HTTPMethod(rawValue: "CONNECT")
public static let delete = HTTPMethod(rawValue: "DELETE")
public static let get = HTTPMethod(rawValue: "GET")
public static let head = HTTPMethod(rawValue: "HEAD")
public static let options = HTTPMethod(rawValue: "OPTIONS")
public static let patch = HTTPMethod(rawValue: "PATCH")
public static let post = HTTPMethod(rawValue: "POST")
public static let put = HTTPMethod(rawValue: "PUT")
public static let trace = HTTPMethod(rawValue: "TRACE")

public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}
}

parameters

这个非常简单,就是一个String-Any类型的字典,用来接收用户这次请求想要传递的参数

1
public typealias Parameters = [String: Any]

ParameterEncoding

ParameterEncoding是一个协议类型,该协议中只定义了一个encode方法,该方法实际的作用是将给定的参数parameters按照某种特定的策略编码,并将编码后的数据放到request合适的位置(比如get请求放到URL中,post请求经常需要将数据放到body中)

1
2
3
public protocol ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}

不过编码这件事情听起来简单,但其实需要处理很多细节,所以Alamofire这样完备的框架一定不会只提供一个协议让你自己去实现,相反,你在日常开发中几乎不需要自定义ParameterEncoding的协议实现,Alamofire系统中提供的两个遵循协议的结构体基本可以满足我们日常开发中的需求。这两个结构体分别是URLEncoding与JSONEncoding,我们先来分析URLEncoding的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* 在结构体的开头先定义了一系列内部类型 */
public struct URLEncoding: ParameterEncoding {
/* 该类型表示编码后的数据会被添加到哪里去 */
public enum Destination {
/* methodDependent表示会依靠方法来确定编码后的数据会添加到哪里,如果是GET,DELETE,HEAD则会将数据添加到URL末尾,其他方法则会添加到HTTP Body中,这个是默认的 */
case methodDependent
/* 将编码后的结果添加到已经存在的URL的末尾作为Query String */
case queryString
/* 将编码后的结果添加到request的HTTP Body中 */
case httpBody
// 返回数据是否会被直接添加到URL中
func encodesParametersInURL(for method: HTTPMethod) -> Bool {
switch self {
case .methodDependent: return [.get, .head, .delete].contains(method)
case .queryString: return true
case .httpBody: return false
}
}
}
/// 该类型表示对于数组类型的参数如何进行编码
/// 例如:["arr": [1,2,3]]这样的参数
public enum ArrayEncoding {
/// 该值表示编码时键后面都会跟一个方括号:arr[]=0&arr[]=1&arr[]=2
case brackets
/// 按照键本身来编码:arr=0&arr=1&arr=2
case noBrackets
/// 输入键,输出编码后的键名
func encode(key: String) -> String {
switch self {
case .brackets:
return "\(key)[]"
case .noBrackets:
return key
}
}
}
/// 该类型表示对于布尔类型的参数如何进行编码
public enum BoolEncoding {
/// true编码为1,false编码为0
case numeric
/// true编码为“true”, false编码为“false”
case literal
func encode(value: Bool) -> String {
switch self {
case .numeric:
return value ? "1" : "0"
case .literal:
return value ? "true" : "false"
}
}
}
}

接下来我们来看URLEncoding结构体的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public struct URLEncoding: ParameterEncoding {
/// 以下三个都是该类的单例,default返回所有策略都默认的URLEncoding对象
public static var `default`: URLEncoding { URLEncoding() }
public static var queryString: URLEncoding { URLEncoding(destination: .queryString) }
public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) }
/// 以下三个变量存储上述帮助类型,是三种不同的编码策略。分别是编码数据存储的位置,数组的编码策略,布尔值的编码策略
public let destination: Destination
public let arrayEncoding: ArrayEncoding
public let boolEncoding: BoolEncoding
/// 初始化方法
public init(destination: Destination = .methodDependent,
arrayEncoding: ArrayEncoding = .brackets,
boolEncoding: BoolEncoding = .numeric) {
self.destination = destination
self.arrayEncoding = arrayEncoding
self.boolEncoding = boolEncoding
}

/// 实现了ParameterEncoding协议,声明了encode方法
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
// 拿到URLRequest与parameters
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
// 首先根据destination策略,决定将编码后的数据放入url中还是http body中
if let method = urlRequest.method, destination.encodesParametersInURL(for: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
// 对参数进行各种拼接操作,拼接为queryString的格式
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
// 在这里我们可以看到,如果我们使用URLEncoding的方式发送http body数据,是使用x-www-form-urlencoded的编码方式发送的
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}

urlRequest.httpBody = Data(query(parameters).utf8)
}

return urlRequest
}
}
  1. 先拿到urlRequest与parameters对象
  2. 让destination策略来决定编码后的数据应该被放入url中还是httpBody中
  3. 如果应该被放入url中,那么就调用帮助函数query,将参数编码为queryString的格式,最后与原URL进行拼接,最后再将拼接好的url设置进request。注意urlComponents.percentEncodedQuery是在拿URL中原本就拥有的queryString,如果原本的url中存在一些queryString,那么就会将旧的和新的一起拼接起来
  4. 如果数据是应该被放入httpBody中的,那么就会将request的Content-Type的Head设置为application/x-www-form-urlencoded,并且使用帮助函数query将参数转为queryString之后将字符串转为data,并放入http的body中

接下来让我们来看一下上面的encode方法中所用到的工具函数吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/// 该方法将参数字典,转换为queryString的形式
private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []

for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}

/// 1
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
/// 2
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
/// 3
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
} else if let value = value as? NSNumber {
/// 4
if value.isBool {
/// escape的作用是将普通字符串URL要求转义的部分转义
components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
} else {
components.append((escape(key), escape("\(value)")))
}
/// 4
} else if let bool = value as? Bool {
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
/// 5
} else {
components.append((escape(key), escape("\(value)")))
}

return components
}
  1. queryComponents这个方法参数字典中的一个键值对,而这个键值对的值的类型是Any的,也就是可以是任何类型,这个方法的目的就是针对不同的类型做出不同的转化,最终将一个键值对转换为一个或多个纯字符串键值对。
  2. 如果传进来的值是一个字典,则在编码是会生成 [(key[nestedKey1],val1),(key[nestedKey2], val2)...],并且还会对其中的value继续进行递归
  3. 如果传进来的值是一个数组,则会根据数组数据的编码策略生成 [(a[],1),(a[],2)...] 或者 [(a,1), (a,2),...] 这样的键值数据。
  4. 如果传进来的值是一个布尔值,则按照布尔类型的编码策略,将布尔值转化为”true”/“1”和”false”/“0”这样的字符串
  5. 如果这些类型都不是,则直接将Any类型的对象转换为一个字符串来显示

上述就是关于URLEncoding的所有内容,下面我们再来看一个开发中也经常会被用到的编码器:JSONEncoding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public struct JSONEncoding: ParameterEncoding {
// 不同参数设置的单例对象
public static var `default`: JSONEncoding { JSONEncoding() }
public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }

/// jsonSerialization相关的设置
public let options: JSONSerialization.WritingOptions

public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
/// 实现了ParameterEncoding协议,声明了encode方法
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()

guard let parameters = parameters else { return urlRequest }

do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)

if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}

urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}

return urlRequest
}
}

JSONEncoding的encode方法非常简单:首先获取到参数和request,然后直接使用JSONSerialization将参数字典转换为jsonData,然后将该data放进httpData中,最后设置请求的Content-Type Header为”application/json”,仅仅是这样就完成了jsonEncoding的任务。

HTTPHeaders

该类型很简单,只是用来存储此次请求需要设置的HTTPHeader列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public struct HTTPHeaders {
private var headers: [HTTPHeader]
public init() {}
public init(_ headers: [HTTPHeader]) {
self.init()

headers.forEach { update($0) }
}
public init(_ dictionary: [String: String]) {
self.init()

dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
}

public mutating func add(name: String, value: String) {
update(HTTPHeader(name: name, value: value))
}.
public mutating func add(_ header: HTTPHeader) {
update(header)
}
public mutating func update(name: String, value: String) {
update(HTTPHeader(name: name, value: value))
}
public mutating func update(_ header: HTTPHeader) {
guard let index = headers.index(of: header.name) else {
headers.append(header)
return
}

headers.replaceSubrange(index...index, with: [header])
}
public mutating func remove(name: String) {
guard let index = headers.index(of: name) else { return }

headers.remove(at: index)
}
// 后面还有很多诸如此类的便捷方法,就不全部放出了...
}

RequestInterceptor

RequestInterceptor是Alamofire中定义的继承自两个其他协议的空协议,这两个协议分别是RequestAdapter和RequestRetrier。我们可以通过RequestInterceptor来实现很强大的请求自适应和失败重试功能,比如网络请求发起前的账户检查,添加通用的header以及网络请求失败后使用自定义的重传策略。Session与Request中都有一个属性用来存储RequestInterceptor拦截器,Session中的拦截器可以作用于Session发起的所有Request之上,而Request中的拦截器只能作用到自身Request之上。

1
2
3
4
5
6
7
8
9
public protocol RequestInterceptor: RequestAdapter, RequestRetrier {}

public protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
}

public protocol RequestRetrier {
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
}

RequestAdapter 协议允许在通过网络发出之前检查和修改Session执行的每个URLRequest,一个比较常见的用法是,在某些网络请求发出之前首先检查本地存储的身份验证信息,如果正常则附加到authorization请求头中,如果不正常则放弃请求。RequestAdapter协议中只规定了一个方法adapt,该方法会在每个Session执行每个URLRequest之前执行,方法的第三个参数,是一个回调函数,你在完成判断之后必须调用该回调函数,并传入你判断的结果以继续请求流程,如果允许执行就传递.success,如果不允许继续执行就传递.fail(someError),someError是说明此次不允许继续执行的错误原因

RequestRetrier 协议允许重试在执行时遇到错误的请求。协议中只规定了一个方法retry,该方法的最后一个参数同样也是一个回调,你在判断这个请求是否可以进行重试之后,需要将结果以回调函数参数的方式传递给Alamofire系统以继续重试流程,Alamofire中还定义了一个用来表示判断结果的枚举RetryResult

1
2
3
4
5
6
7
8
9
10
public enum RetryResult {
/// 立即重试
case retry
/// 在TimeInterval之后重试
case retryWithDelay(TimeInterval)
/// 不重试
case doNotRetry
/// 不重试,并把错误传递进去,之后的处理中可以获取到此次的错误
case doNotRetryWithError(Error)
}

另外,系统给RequestInterceptor协议提供了方法默认空实现,这样可以使得在Adapter和Retyier这两个功能只需要其中一种功能的情况下编写起来更加方便,只需要覆写需要的方法就可以了

1
2
3
4
5
6
7
8
9
10
11
12
extension RequestInterceptor {
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
completion(.success(urlRequest))
}

public func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
completion(.doNotRetry)
}
}

Alamofire中还为我们提供了一个实现RequestInterceptor协议的RetryPolicy类,这个类可以快速地帮助我们实现网络错误重试的需求

在RetryPolicy类定义了一系列的默认值,我们可以选择直接忽略这个部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
open class RetryPolicy: RequestInterceptor {

// 开头定义了一系列的默认值,可以直接忽略
public static let defaultRetryLimit: UInt = 2
public static let defaultExponentialBackoffBase: UInt = 2
public static let defaultExponentialBackoffScale: Double = 0.5
public static let defaultRetryableHTTPMethods: Set<HT
public static let defaultRetryableHTTPStatusCodes: Set<Int> = [408, 500, 502, 503, 504 ]
public static let defaultRetryableURLErrorCodes: Set<URLError.Code> = [
.backgroundSessionInUseByAnotherProcess,
.backgroundSessionWasDisconnected,
.badServerResponse,
.callIsActive,
.cannotConnectToHost,
.cannotFindHost,
.cannotLoadFromNetwork,
.dataNotAllowed,
.dnsLookupFailed,
.downloadDecodingFailedMidStream,
.downloadDecodingFailedToComplete,
.internationalRoamingOff,
.networkConnectionLost,
.notConnectedToInternet,
.secureConnectionFailed,
.serverCertificateHasBadDate,
.serverCertificateNotYetValid,
.timedOut
]
}

接下来是RetryPolicy类的核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
open class RetryPolicy: RequestInterceptor {
/// 一个请求最多可以重试多少次
public let retryLimit: UInt
/// 延时时间计算的相关系数,这两个参数的用处可以看下面retry方法中的分析
public let exponentialBackoffBase: UInt
public let exponentialBackoffScale: Double
/// 可以进行重试的方法
public let retryableHTTPMethods: Set<HTTPMethod>
/// 可以进行重试的响应状态码
public let retryableHTTPStatusCodes: Set<Int>
/// 可以进行重试的错误
public let retryableURLErrorCodes: Set<URLError.Code>
/// 初始化方法中所有参数都提供了默认值
public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit,
exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase,
exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale,
retryableHTTPMethods: Set<HTTPMethod> = RetryPolicy.defaultRetryableHTTPMethods,
retryableHTTPStatusCodes: Set<Int> = RetryPolicy.defaultRetryableHTTPStatusCodes,
retryableURLErrorCodes: Set<URLError.Code> = RetryPolicy.defaultRetryableURLErrorCodes) {
precondition(exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2.")

self.retryLimit = retryLimit
self.exponentialBackoffBase = exponentialBackoffBase
self.exponentialBackoffScale = exponentialBackoffScale
self.retryableHTTPMethods = retryableHTTPMethods
self.retryableHTTPStatusCodes = retryableHTTPStatusCodes
self.retryableURLErrorCodes = retryableURLErrorCodes
}
/// 重试方法
open func retry(_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
// 1
if request.retryCount < retryLimit,
let httpMethod = request.request?.method,
retryableHTTPMethods.contains(httpMethod),
shouldRetry(response: request.response, error: error) {
// 2
let timeDelay = pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale
completion(.retryWithDelay(timeDelay))
} else {
completion(.doNotRetry)
}
}
private func shouldRetry(response: HTTPURLResponse?, error: Error) -> Bool {
if let statusCode = response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) {
return true
} else {
let errorCode = (error as? URLError)?.code
let afErrorCode = (error.asAFError?.underlyingError as? URLError)?.code
if let code = errorCode ?? afErrorCode, retryableURLErrorCodes.contains(code) {
return true
} else {
return false
}
}
}

}
  1. 首先会进行一系列的条件判断,判断当前发生错误的request是否满足可以进行重试的条件。具体的条件包括:状态码是否在可重试状态码集合中,请求方法是否在可重试请求方法集合中,发生的错误Error是否在可重试错误集合中。如果判断通过,则开始计算重试的延时事件,如果判断不通过,则不允许重试
  2. 系统提供的RetryPolicy使用延时重试的策略,基于提供的相关系数以及重试的次数计算出延时的时间,是关于重试次数的指数函数,重试次数越多,延时越久

RequestModifier

RequestModifier的定义很简单,其定义的目的就是为了增强可扩展性,允许用户对request做出更加细化的修改和定制

typealias RequestModifier = (inout URLRequest) throws -> Void

系统会在创建了URLRequest之后,对其调用resume之前,调用你传入的RequestModifier闭包。

本小节所要分析的源码已经全部展示完毕了,未完待续……

--- 本文结束 The End ---