- Alamofire 5 的使用 - 高级用法
这篇文章介绍的是 Alamofire 框架的高级用法,如果之前没有看过基本用法的,可以先去看看 Alamofire 5 的使用 - 基本用法
Alamofire 是建立在 URLSession
和 URL 加载系统之上的。为了充分利用这个框架,建议您熟悉底层网络的概念和功能。
建议阅读:
- URL Loading System Programming Guide
URLSession
Class ReferenceURLCache
Class ReferenceURLAuthenticationChallenge
Class Reference
Alamofire 的 Session
在职责上大致等同于它维护的 URLSession
实例:它提供 API 来生成各种 Request
子类,这些子类封装了不同的 URLSessionTask
子类,以及封装应用于实例生成的所有 Request
的各种配置。
Session
提供了一个 default
单例实例,并且 AF
实际上就是 Session.default
。因此,以下两个语句是等效的:
AF.request("https://httpbin.org/get")
let session = Session.default
session.request("https://httpbin.org/get")
大多数应用程序将需要以各种方式自定义其 Session
实例的行为。实现这一点的最简单方法是使用以下便利初始化器,并将结果存储在整个应用程序中使用的单个实例中。
public convenience init(
configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
delegate: SessionDelegate = SessionDelegate(),
rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
startRequestsImmediately: Bool = true,
requestQueue: DispatchQueue? = nil,
serializationQueue: DispatchQueue? = nil,
interceptor: RequestInterceptor? = nil,
serverTrustManager: ServerTrustManager? = nil,
redirectHandler: RedirectHandler? = nil,
cachedResponseHandler: CachedResponseHandler? = nil,
eventMonitors: [EventMonitor] = []
)
此初始化器允许自定义 Session
的所有基本行为。
要自定义底层 URLSession
的行为,可以提供自定义的 URLSessionConfiguration
实例。建议从 URLSessionConfiguration.af.default
实例开始,因为它添加了 Alamofire 提供的默认 Accept-Encoding
、Accept-Language
和User-Agent
headers,但是可以使用任何 URLSessionConfiguration
。
let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false
let session = Session(configuration: configuration)
URLSessionConfiguration
不是设置Authorization
或Content-Type
headers 的建议位置。相反,可以使用提供的headers
APIs、ParameterEncoder
或RequestAdapter
将它们添加到Request
中。
正如苹果在其文档中所述,在实例被添加到
URLSession
(或者,在 Alamofire 的情况下,用于初始化Session
)之后对URLSessionConfiguration
属性进行修改没有效果。
SessionDelegate
实例封装了对各种 URLSessionDelegate
和相关协议回调的所有处理。SessionDelegate
还充当 Alamofire 生成的每个 Request
的 SessionStateDelegate
,允许 Request
从创建它们的 Session
实例间接导入状态。SessionDelegate
可以使用特定的 FileManager
实例进行自定义,该实例将用于任何磁盘访问,例如访问要通过 UploadRequest
上传的文件或通过 DownloadRequest
下载的文件。
let delelgate = SessionDelegate(fileManager: .default)
默认情况下,Session
将在添加至少一个响应 handler 后立即对 Request
调用 resume()
。将 startRequestsImmediately
设置为 false
需要手动调用所有请求的 resume()
方法。
let session = Session(startRequestsImmediately: false)
默认情况下,Session
实例对所有异步工作使用单个 DispatchQueue
。这包括 URLSession
的 delegate
OperationQueue
的 underlyingQueue
,用于所有 URLRequest
创建、所有响应序列化工作以及所有内部 Session
和 Request
状态的改变。如果性能分析显示瓶颈在于 URLRequest
的创建或响应序列化,则可以为 Session
的每个工作区域提供单独的 DispatchQueue
。
let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")
let session = Session(
rootQueue: rootQueue,
requestQueue: requestQueue,
serializationQueue: serializationQueue
)
提供的任何自定义 rootQueue
都必须是串行队列,但 requestQueue
和 serializationQueue
可以是串行或并行队列。通常建议使用串行队列,除非性能分析显示工作被延迟,在这种情况下,使队列并行可能有助于提高整体性能。
Alamofire 的 RequestInterceptor
协议(RequestAdapter & RequestRetrier
)提供了重要而强大的请求自适应和重试功能。它可以在 Session
和 Request
层级使用。有关 RequestInterceptor
和 Alamofire 包含的各种实现(如 RetryPolicy
)的更多详细信息,请参见下文。
let policy = RetryPolicy()
let session = Session(interceptor: policy)
Alamofire 的 ServerTrustManager
类封装了域名和遵循 ServerTrustEvaluating
协议的类型实例之间的映射,这提供了定制 Session
处理 TLS 安全性的能力。这包括使用证书和公钥固定以及证书吊销检查。有关更多信息,请参阅有关 ServerTrustManager
和 ServerTrustEvaluating
的部分。初始化 ServerTrustManger
非常简单,只需提供域名与要执行的计算类型之间的映射即可:
let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let session = Session(serverTrustManager: manager)
有关评估服务器信任的详细信息,请参阅下面的详细文档。
Alamofire 的 RedirectHandler
协议定制了 HTTP 重定向响应的处理。它可以在 Session
和 Request
层级使用。Alamofire 包含了遵循 RedirectHandler
协议的 Redirector
类型,并提供对重定向的简单控制。有关重定向处理程序的详细信息,请参阅下面的详细文档。
let redirector = Redirector(behavior: .follow)
let session = Session(redirectHandler: redirector)
Alamofire 的 CachedResponseHandler
协议定制了响应的缓存,可以在 Session
和 Request
层级使用。Alamofire 包含 ResponseCacher
类型,它遵循 CachedResponseHandler
协议并提供对响应缓存的简单控制。有关详细信息,请参阅下面的详细文档。
let cacher = ResponseCacher(behavior: .cache)
let session = Session(cachedResponseHandler: cacher)
Alamofire 的 EventMonitor
协议提供了对 Alamofire 内部事件的强大洞察力。它可以用来提供日志和其他基于事件的特性。Session
在初始化时接受遵循 EventMonitor
协议的实例的数组。
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
debugPrint(request)
}
let session = Session(eventMonitors: [monitor])
除了前面提到的便利初始化器之外,还可以直接从 URLSession
初始化 Session
。但是,在使用这个初始化器时需要记住几个要求,因此建议使用便利初始化器。其中包括:
- Alamofire 不支持为在后台使用而配置的
URLSession
。初始化Session
时,这将导致运行时错误。 - 必须创建
SessionDelegate
实例并将其作为URLSession
的delegate
,以及传递给Session
的初始化器。 - 必须将自定义
OperationQueue
作为URLSession
的delegateQueue
。此队列必须是串行队列,它必须具有备用DispatchQueue
,并且必须将该DispatchQueue
作为其rootQueue
传递给Session
。
let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate()
let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
delegate: delegate,
delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)
Alamofire 执行的每个请求都由特定的类、DataRequest
、UploadRequest
和 DownloadRequest
封装。这些类中的每一个都封装了每种类型请求所特有的功能,但是 DataRequest
和 DownloadRequest
继承自一个公共的父类 Request
(UploadRequest
继承自 DataRequest
)。Request
实例从不直接创建,而是通过各种 request
方法之一从会话 Session
中自动生成。
一旦使用 Request
子类的初始参数或 URLRequestConvertible
创建了它,它就会通过组成 Alamofire 请求管道的一系列步骤进行传递。对于成功的请求,这些请求包括:
- 初始参数(如 HTTP 方法、headers 和参数)被封装到内部
URLRequestConvertible
值中。如果直接传递URLRequestConvertible
值,则使用该值时将保持不变。 - 对
URLRequestConvertible
值调用asURLRequest()
,创建第一个URLRequest
值。此值将传递给Request
并存储在requests
中。 - 如果有任何
Session
或Request
RequestAdapter
或RequestInterceptor
,则使用先前创建的URLRequest
调用它们。然后将调整后的URLRequest
传递给Request
并存储在requests
中。 Session
调用Request
创建的URLSessionTask
,以基于URLRequest
执行网络请求。- 完成
URLSessionTask
并收集URLSessionTaskMetrics
后,Request
将执行其Validator
。 - 请求执行已附加的任何响应 handlers,如
responseDecodable
。
在这些步骤中的任何一个,都可以通过创建或接收的 Error
值来表示失败,然后将错误值传递给关联的 Request
。例如,除了步骤 1 和 4 之外,上面的所有其他步骤都可以创建一个Error
,然后传递给响应 handlers 或可供重试。下面是一些可以或不能在整个请求管道中失败的示例。
- 参数封装不能失败。
- 调用
asURLRequest()
时,任何URLRequestConvertible
值都可能创建错误。这允许初始验证各种URLRequest
属性或参数编码失败。 RequestAdapter
在自适应过程中可能会失败,可能是由于缺少授权 token。URLSessionTask
创建不能失败。URLSessionTask
可能由于各种原因带有错误地完成,包括网络可用性和取消。这些Error
值将传递回给Request
。- 响应 handlers 可以产生任何错误,通常是由于无效响应或其他分析错误。
一旦将错误传递给 Request
,Request
将尝试运行与 Session
或 Request
关联的任何 RequestRetrier
。如果任何 RequestRetrier
选择重试该 Request
,则将再次运行完整的管道。RequestRetrier
也会产生 Error
,但这些错误不会触发重试。
尽管 Request
不封装任何特定类型的请求,但它包含 Alamofire 执行的所有请求所共有的状态和功能。这包括:
所有 Request
类型都包含状态的概念,表示 Request
生命周期中的主要事件。
public enum State {
case initialized
case resumed
case suspended
case cancelled
case finished
}
请求在创建后以 .initialized
状态启动。通过调用适当的生命周期方法,可以挂起、恢复和取消 Request
。
resume()
恢复或启动请求的网络流量。如果startRequestsImmediately
为true
,则在将响应 handlers 添加到Request
后自动调用此函数。suspend()
挂起或暂停请求及其网络流量。此状态下的Request
可以继续,但只有DownloadRequest
才能继续传输数据。其他Request
将重新开始。cancel()
取消请求。一旦进入此状态,就无法恢复或挂起Request
。调用cancel()
时,将使用AFError.explicitlyCancelled
实例设置请求的error
属性。如果一个Request
被恢复并且在以后没有被取消,那么它将在所有响应验证器和响应序列化器运行之后到达.finished
状态。但是,如果在请求达到.finished
状态后将其他响应序列化器添加到该请求,则它将转换回.resumed
状态并再次执行网络请求。
为了跟踪请求的进度,Request
提供了 uploadProgress
和 downloadProgress
属性以及基于闭包的 uploadProgress
和 downloadProgress
方法。与所有基于闭包的 Request
APIs 一样,进度 APIs 可以与其他方法链接到 Request
之外。与其他基于闭包的 APIs 一样,它们应该在添加任何响应 handlers(如 responseDecodable
)之前添加到请求中。
AF.request(...)
.uploadProgress { progress in
print(progress)
}
.downloadProgress { progress in
print(progress)
}
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
重要的是,并不是所有的 Request
子类都能够准确地报告它们的进度,或者可能有其他依赖项来报告它们的进度。
- 对于上传进度,可以通过以下方式确定进度:
- 通过作为上传 body 提供给
UploadRequest
的Data
对象的长度。 - 通过作为
UploadRequest
的上传 body 提供的磁盘上文件的长度。 - 通过根据请求的
Content-Length
header 的值(如果已手动设置)。
- 通过作为上传 body 提供给
- 对于下载进度,只有一个要求:
- 服务器响应必须包含
Content-Length
header。不幸的是,URLSession
对进度报告可能还有其他未记录的要求,这妨碍了准确的进度报告。
- 服务器响应必须包含
Alamofire 的 RedirectHandler
协议提供了对 Request
的重定向处理的控制和定制。除了每个 Session
RedirectHandler
之外,每个 Request
都可以被赋予属于自己的 RedirectHandler
,并且这个 handler 将重写 Session
提供的任何 RedirectHandler
。
let redirector = Redirector(behavior: .follow)
AF.request(...)
.redirect(using: redirector)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
注意:一个
Request
只能设置一个RedirectHandler
。尝试设置多个将导致运行时异常。
Alamofire 的 CachedResponseHandler
协议提供了对响应缓存的控制和定制。除了每个 Session
的 CachedResponseHandlers
之外,每个 Request
都可以被赋予属于自己的 CachedResponseHandler
,并且这个 handler 将重写 Session
提供的任何 CachedResponseHandler
。
let cacher = Cacher(behavior: .cache)
AF.request(...)
.cacheResponse(using: cacher)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
注意:一个
Request
只能设置一个CachedResponseHandler
。尝试设置多个将导致运行时异常。
为了利用 URLSession
提供的自动凭证处理,Alamofire 提供了每个 Request
API,允许向请求自动添加 URLCredential
实例。这包括使用用户名和密码进行 HTTP 身份验证的便利 API,以及任何 URLCredential
实例。
添加凭据以自动答复任何 HTTP 身份验证质询很简单:
AF.request(...)
.authenticate(username: "user@example.domain", password: "password")
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
注意:此机制仅支持 HTTP 身份验证提示。如果一个请求需要一个用于所有请求的
Authentication
header,那么应该直接提供它,或者作为请求的一部分,或者通过一个RequestInterceptor
。
此外,添加 URLCredential
也同样简单:
let credential = URLCredential(...)
AF.request(...)
.authenticate(using: credential)
.responseDecodable(of: SomeType.self) { response in
debugPrint(response)
}
由 Request
发出的每个网络请求最终封装在由传递给 Session
请求方法之一的各种参数创建的 URLRequest
值中。Request
将在其 requests
数组属性中保留这些 URLRequest
的副本。这些值既包括从传递的参数创建的初始 URLRequest
,也包括由 RequestInterceptors
创建的任何 URLRequest
。但是,该数组不包括代表 Request
发出的 URLSessionTask
执行的 URLRequest
。要检查这些值,tasks
属性允许访问 Request
执行的所有 URLSessionTask
。
在许多方面,各种 Request
子类充当 URLSessionTask
的包装器,提供与特定类型任务交互的特定 API。这些任务通过 tasks
数组属性在 Request
实例上可见。这包括为 Request
创建的初始任务,以及作为重试过程的一部分创建的任何后续任务,每次重试一个任务。
请求完成后,每个 Request
可能都有一个可用的 HTTPURLResponse
值。此值仅在请求未被取消且没有发出网络请求失败时可用。此外,如果重试请求,则只有最后一个响应可用。可以从 tasks 属性中的 URLSessionTasks
获得中间的响应。
Alamofire 为 Request
执行的每个 URLSessionTask
收集 URLSessionTaskMetrics
值。这些值存储在 metrics
属性,每个值对应于同一索引中的 tasks
中的 URLSessionTask
。
URLSessionTaskMetrics
也可从 Alamofire 的各种响应类型中访问,如 DataResponse
。例如:
AF.request(...)
.responseDecodable(of: SomeType.self) { response in {
print(response.metrics)
}
DataRequest
是 Request
的一个子类,它封装了 URLSessionDataTask
,将服务器响应下载到存储在内存中的 Data
中。因此,必须认识到,超大下载量可能会对系统性能产生不利影响。对于这些类型的下载,建议使用 DownloadRequest
将数据保存到磁盘。
除了 Request
提供的属性之外,DataRequest
还有一些属性。其中包括 data
(这是服务器响应的累积 Data
)和 convertible
(这是创建 DataRequest
时使用的 URLRequestConvertible
),其中包含创建实例的原始参数。
默认情况下,DataRequest
不验证响应。相反,必须向其中添加对 validate()
的调用,以验证各种属性是否有效。
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> Result<Void, Error>
默认情况下,添加 validate()
确保响应状态代码在 200..<300
范围内,并且响应的 Content-Type
与请求的 Accept
匹配。通过传递 Validation
闭包可以进一步定制验证:
AF.request(...)
.validate { request, response, data in
...
}
UploadRequest
是 DataRequest
的一个子类,它封装 URLSessionUploadTask
、将 Data
、磁盘上的文件或 InputStream
上传到远程服务器。
除了 DataRequest
提供的属性外,UploadRequest
还有一些属性。其中包括一个 FileManager
实例,用于在上传文件时自定义对磁盘的访问,以及 upload
,upload
封装了用于描述请求的 URLRequestConvertible
值和确定要执行的上传类型的 Uploadable
值。
DownloadRequest
是 Request
的一个具体子类,它封装了 URLSessionDownloadTask
,将响应数据下载到磁盘。
DownloadRequest
除了由 Request
提供的属性外,还有一些属性。其中包括取消 DownloadRequest
时生成的数据 resumeData
(可用于以后继续下载)和 fileURL
(下载完成后下载文件对应的 URL)。
除了支持 Request
提供的 cancel()
方法外,DownloadRequest
还包括 cancel(producingResumeData shouldProduceResumeData: Bool)
,如果可能的话,可以选择在取消时设置 resumeData
属性,以及 cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void)
,它将生成的恢复数据提供给传递进来的闭包。
AF.download(...)
.cancel { resumeData in
...
}
DownloadRequest
支持的验证版本与 DataRequest
和 UploadRequest
略有不同,因为它的数据被下载到磁盘上。
public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse, _ fileURL: URL?)
必须使用提供的 fileURL
访问下载的 Data
,而不是直接访问下载的 Data。否则,DownloadRequest
的验证器的功能与 DataRequest
的相同。
Alamofire 的 RequestInterceptor
协议(由 RequestAdapter
和 RequestRetrier
协议组成)支持强大的每个 Session
和每个 Request
功能。其中包括身份验证系统,在该系统中,向每个 Request
添加一个常用的 headers,并在授权过期时重试 Request
。此外,Alamofire 还包含一个内置的 RetryPolicy
类型,当由于各种常见的网络错误而导致请求失败时,可以轻松重试。
Alamofire 的 RequestAdapter
协议允许在通过网络发出之前检查和修改 Session
执行的每个 URLRequest
。适配器的一个非常常见的用途,是在特定类型身份验证后面将 Authorization
header 添加请求。
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
它的参数包括:
urlRequest
:最初从用于创建请求的参数或URLRequestConvertible
值创建的urlRequest
。session
:创建调用适配器的Request
的Session
。completion
: 一个必须调用的、用来表示适配器已完成的异步 completion handler。它的异步特性使RequestAdapter
能够在请求通过网络发送之前从网络或磁盘访问异步资源。提供给completion
闭包的Result
可以返回带有修改后的URLRequest
的.success
值,或者返回带有关联错误的.failure
值,然后将使用该值使请求失败。例如,添加Authorization
header 需要修改URLRequest
,然后调用completion
。
let accessToken: String
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.headers.add(.authorization(bearer: accessToken))
completion(.success(urlRequest))
}
Alamofire 的 RequestRetrier
协议允许重试在执行时遇到错误的请求。这包括在 Alamofire 的请求管道的任何阶段产生的错误。
RequestRetrier
协议只有一个方法:
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
它的参数包括:
request
: 遇到错误的Request
。session
: 管理Request
的Session
。error
: 触发重试的Error
,通常是一个AFError
。completion
: 必须调用的异步 completion handler,以表示Request
是否需要重试。调用的时候必须传入RetryResult
。
RetryResult
类型表示在 RequestRetrier
中实现的任何逻辑的结果。定义为:
/// Outcome of determination whether retry is necessary.
public enum RetryResult {
/// Retry should be attempted immediately.
case retry
/// Retry should be attempted after the associated `TimeInterval`.
case retryWithDelay(TimeInterval)
/// Do not retry.
case doNotRetry
/// Do not retry due to the associated `Error`.
case doNotRetryWithError(Error)
}
例如,如果请求是等幂的,Alamofire 的 RetryPolicy
类型将自动重试由于某种网络错误而失败的请求。
open func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
if request.retryCount < retryLimit,
let httpMethod = request.request?.method,
retryableHTTPMethods.contains(httpMethod),
shouldRetry(response: request.response, error: error) {
let timeDelay = pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale
completion(.retryWithDelay(timeDelay))
} else {
completion(.doNotRetry)
}
}
在与服务器和 web 服务通信时使用安全的 HTTPS 连接是保护敏感数据的重要步骤。默认情况下,Alamofire 接收与 URLSession
相同的自动 TLS 证书和证书链验证。虽然这保证了证书链的有效性,但并不能防止中间人(MITM)攻击或其他潜在的漏洞。为了减轻 MITM 攻击,处理敏感客户数据或财务信息的应用程序应使用 Alamofire 的 ServerTrustEvaluating
协议提供的证书或公钥固定。
ServerTrustEvaluting
协议提供了执行任何类型服务器信任评估的方法。它只有一个方法:
func evaluate(_ trust: SecTrust, forHost host: String) throws
此方法提供从底层 URLSession
接收的 SecTrust
值和主机 String
,并提供执行各种评估的机会。
Alamofire 包括许多不同类型的信任评估器,为评估过程提供可组合的控制:
DefaultTrustEvaluator
:使用默认服务器信任评估,同时允许您控制是否验证质询提供的主机。RevocationTrustEvaluator
:检查接收到的证书的状态以确保它没有被吊销。这通常不会在每个请求上执行,因为它需要网络请求开销。PinnedCertificatesTrustEvaluator
: 使用提供的证书验证服务器信任。如果某个固定证书与某个服务器证书匹配,则认为服务器信任有效。此评估器还可以接受自签名证书。PublicKeysTrustEvaluator
: 使用提供的公钥验证服务器信任。如果某个固定公钥与某个服务器证书公钥匹配,则认为服务器信任有效。CompositeTrustEvaluator
: 评估一个ServerTrustEvaluating
值数组,只有在所有数组中值都成功时才成功。此类型可用于组合,例如,RevocationTrustEvaluator
和PinnedCertificatesTrustEvaluator
。DisabledEvaluator
:此评估器应仅在调试方案中使用,因为它禁用所有求值,而这些求值又将始终认为任何服务器信任都是有效的。此评估器不应在生产环境中使用!
ServerTrustManager
负责存储 ServerTrustEvaluating
值到特定主机的内部映射。这允许 Alamofire 使用不同的评估器评估每个主机。
let evaluators: [String: ServerTrustEvaluating] = [
// 默认情况下,包含在 app bundle 的证书会自动固定。
"cert.example.com": PinnedCertificatesTrustEvalutor(),
// 默认情况下,包含在 app bundle 的来自证书的公钥会被自动使用。
"keys.example.com": PublicKeysTrustEvalutor(),
]
let manager = ServerTrustManager(evaluators: serverTrustPolicies)
此 ServerTrustManager
将具有以下行为:
cert.example.com
将始终在启用默认和主机验证的情况下使用证书固定,因此需要满足以下条件才能允许 TLS 握手成功:- 证书链必须有效。
- 证书链必须包含一个固定证书。
- 质询主机必须与证书链的叶证书中的主机匹配。
keys.example.com
将始终在启用默认和主机验证的情况下使用公钥固定,因此需要满足以下条件才能允许 TLS 握手成功:- 证书链必须有效。
- 证书链必须包含一个固定的公钥。
- 质询主机必须与证书链中的证书中的主机匹配。
- 对其他主机的请求将产生一个错误,因为服务器信任管理器要求默认评估所有主机。
如果发现自己需要更灵活的服务器信任策略匹配行为(例如通配符域名),那么子类化 ServerTrustManager
,并用自己的自定义实现重写 serverTrustEvaluator(forHost:)
方法。
final class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
override func serverTrustEvaluator(forHost host: String) -> ServerTrustEvaluating? {
var policy: ServerTrustPolicy?
// Implement your custom domain matching behavior...
return policy
}
}
在 iOS 9 中添加了 App Transport Security(ATS),使用带有多个 ServerTrustEvaluating
对象的自定义 ServerTrustManager
可能不会有任何效果。如果您持续看到 CFNetwork SSLHandshake failed (-9806)
错误,则可能遇到了此问题。苹果的 ATS 系统会覆盖整个质询系统,除非您在应用程序的 plist 中配置 ATS 设置以禁用足够多的 ATS 设置,以允许您的应用程序评估服务器信任。如果遇到此问题(自签名证书的概率很高),可以通过将 NSAppTransportSecurity
设置添加到 Info.plist
来解决此问题。您可以使用 nscurl
工具的 --ats-diagnostics
选项对主机执行一系列测试,以查看可能需要哪些 ATS 重写。
如果尝试连接到本地主机上运行的服务器,并且使用自签名证书,则需要将以下内容添加到 Info.plist
中。
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
根据苹果文档,将 NSAllowsLocalNetworking
设置为 YES
允许加载本地资源,而不必为应用程序的其余部分禁用 ATS。
URLSession
允许使用 URLSessionDataDelegate
和 URLSessionTaskDelegate
方法自定义缓存和重定向行为。Alamofire 将这些定制点呈现为 CachedResponseHandler
和 RedirectHandler
协议。
CachedResponseHandler
协议允许控制将 HTTP 响应缓存到与发出请求的 Session
相关联的 URLCache
实例中。该协议只有一个方法:
func dataTask(_ task: URLSessionDataTask,
willCacheResponse response: CachedURLResponse,
completion: @escaping (CachedURLResponse?) -> Void)
从方法签名中可以看出,此控制仅适用于使用底层 URLSessionDataTask
进行网络传输的 Request
,这些请求包括 DataRequest
和 UploadRequest
(因为 URLSessionUploadTask
是 URLSessionDataTask
的一个子类)。考虑响应进行缓存的条件非常广泛,因此最好查看 URLSessionDataDelegate
方法 urlSession(_:dataTask:willCacheResponse:completionHandler:)
的文档。一旦考虑将响应用于缓存,就可以进行各种有价值的操作:
- 通过返回
nil
CachedURLResponse
来防止完全缓存响应。 - 修改
CachedURLResponse
的storagePolicy
,以更改缓存值的存放位置。 - 直接修改底层
URLResponse
,添加或删除值。 - 修改与响应关联的
Data
(如果有)。
Alamofire 包含遵循 CachedResponseHandler
协议的 ResponseCacher
类型,使缓存(或者不缓存)或修改响应变得容易。ResponseCacher
接受一个 Behavior
值来控制缓存行为。
public enum Behavior {
/// Stores the cached response in the cache.
case cache
/// Prevents the cached response from being stored in the cache.
case doNotCache
/// Modifies the cached response before storing it in the cache.
case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)
}
ResponseCacher
可以在 Session
和 Request
的基础上使用,如上所述。
RedirectHandler
协议允许控制特定 Request
的重定向行为。它只有一个方法:
func task(_ task: URLSessionTask,
willBeRedirectedTo request: URLRequest,
for response: HTTPURLResponse,
completion: @escaping (URLRequest?) -> Void)
此方法提供了修改重定向的 URLRequest
或传递 nil
以完全禁用重定向的机会。Alamofire 提供了遵循 RedirectHandler
协议的 Redirector
类型,使其易于 follow、not follow 或修改重定向请求。Redirector
接受一个 Behavior
值来控制重定向行为。
public enum Behavior {
/// Follow the redirect as defined in the response.
case follow
/// Do not follow the redirect defined in the response.
case doNotFollow
/// Modify the redirect request defined in the response.
case modify((URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?)
}
EventMonitor
协议允许观察和检查大量内部 Alamofire 事件。这些事件包括由 Alamofire 实现的所有 URLSessionDelegate
、URLSessionTaskDelegate
和 URLSessionDownloadDelegate
方法以及大量内部 Request
事件。除了这些事件(默认情况下是不起作用的空方法)之外,EventMonitor
协议还需要一个 DispatchQueue
,在这个 DispatchQueue
上调度所有事件以保持性能。此 DispatchQueue
默认为 .main
,但对于任何自定义一致类型,建议使用专用串行队列。
也许 EventMonitor
协议的最大用途是实现相关事件的日志记录。一个简单的实现可能如下所示:
final class Logger: EventMonitor {
let queue = DispatchQueue(label: ...)
// Event called when any type of Request is resumed.
func requestDidResume(_ request: Request) {
print("Resuming: \(request)")
}
// Event called whenever a DataRequest has parsed a response.
func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
debugPrint("Finished: \(response)")
}
}
此 Logger
类型可以按上述方法添加到 Session
中:
let logger = Logger()
let session = Session(eventMonitors: [logger])
作为一个框架,Alamofire 有两个主要目标:
- 使原型和工具的网络请求易于实现
- 作为 APP 网络请求的通用基础
它通过使用强大的抽象、提供有用的默认值和包含常见任务的实现来实现这些目标。然而,一旦 Alamofire 的使用超出了一些请求,就有必要超越高级的、默认的实现,进入为特定应用程序定制的行为。Alamofire 提供 URLConvertible
和 URLRequestConvertible
协议来帮助进行这种定制。
可以使用遵循 URLConvertible
协议的类型来构造 URL,然后使用 URL 在内部构造 URL 请求。默认情况下,String
、URL
和 URLComponents
遵循了 URLConvertible
协议,允许将它们中的任何一个作为 URL 参数传递给 request
、upload
和 download
方法:
let urlString = "https://httpbin.org/get"
AF.request(urlString)
let url = URL(string: urlString)!
AF.request(url)
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
AF.request(urlComponents)
鼓励以有意义的方式与 web 应用程序交互的应用程序具有遵循 URLConvertible
的自定义类型,这是将特定于域的模型映射到服务器资源的一种方便方法。
遵循 URLRequestConvertible
协议的类型可用于构造 URLRequest
。默认情况下,URLRequest
遵循 URLRequestConvertible
,允许将其直接传递到 request
、upload
和 download
方法中。Alamofire 使用 URLRevestExchange
作为请求管道中流动的所有请求的基础。直接使用 URLRequest
是在 Alamofire 提供的 ParamterEncoder
之外自定义 URLRequest
创建的推荐方法。
let url = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: url)
urlRequest.method = .post
let parameters = ["foo": "bar"]
do {
urlRequest.httpBody = try JSONEncoder().encode(parameters)
} catch {
// Handle error.
}
urlRequest.headers.add(.contentType("application/json"))
AF.request(urlRequest)
鼓励以有意义的方式与 web 应用程序交互的应用程序具有遵循 URLRequestConvertible
的自定义类型,以确保所请求端点的一致性。这种方法可以用来消除服务器端的不一致性,提供类型安全的路由,以及管理其他状态。
随着应用程序规模的增长,在构建网络堆栈时采用通用模式非常重要。该设计的一个重要部分是如何路由您的请求。Alamofire URLConvertible
和 URLRequestConvertible
协议以及 Router
设计模式都可以帮助您。
“router” 是定义“路由”或请求组件的类型。这些组件可以包括 URLRequest
的部分、发出请求所需的参数以及每个请求的各种 Alamofire 设置。一个简单的 router 可能看起来像这样:
enum Router: URLRequestConvertible {
case get, post
var baseURL: URL {
return URL(string: "https://httpbin.org")!
}
var method: HTTPMethod {
switch self {
case .get: return .get
case .post: return .post
}
}
var path: String {
switch self {
case .get: return "get"
case .post: return "post"
}
}
func asURLRequest() throws -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
return request
}
}
AF.request(Router.get)
更复杂的 router 可以包括请求的参数。使用 Alamofire 的 ParameterEncoder
协议和包含的编码器,任何 Encodable
类型都可以用作参数:
enum Router: URLRequestConvertible {
case get([String: String]), post([String: String])
var baseURL: URL {
return URL(string: "https://httpbin.org")!
}
var method: HTTPMethod {
switch self {
case .get: return .get
case .post: return .post
}
}
var path: String {
switch self {
case .get: return "get"
case .post: return "post"
}
}
func asURLRequest() throws -> URLRequest {
let url = baseURL.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
switch self {
case let .get(parameters):
request = try URLEncodedFormParameterEncoder().encode(parameters, into: request)
case let .post(parameters):
request = try JSONParameterEncoder().encode(parameters, into: request)
}
return request
}
}
Router 可以扩展到具有任意数量可配置属性的任意数量的端点,但是一旦达到了一定的复杂程度,就应该考虑将一个大的 router 分成较小的 router 作为 API 的一部分。
Alamofire 通过各种 response
方法和 ResponseSerializer
协议提供响应处理。
DataRequest
和 DownloadRequest
都提供了一些方法,这些方法允许在不调用任何 ResponseSerializer
的情况下进行响应处理。对于无法将大文件加载到内存中的 DownloadRequest
,这一点最为重要。
// DataRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self
// DownloadRequest
func response(queue: DispatchQueue = .main, completionHandler: @escaping (AFDownloadResponse<URL?>) -> Void) -> Self
与所有响应 handlers 一样,所有序列化工作(在本例中为“无”)都在内部队列上执行,并在传递给方法的 queue
上调用 completion handler。这意味着在默认情况下不需要将其分派回主队列。但是,如果要在 completion handler 中执行任何重要的工作,建议将自定义队列传递给响应方法,必要时在 handler 本身中将分派回主队列。
ResponseSerializer
协议由 DataResponseSerializerProtocol
和 DownloadResponseSerializerProtocol
协议组成。ResponseSerializer
的组合版本如下:
public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
/// The type of serialized object to be created.
associatedtype SerializedObject
/// `DataPreprocessor` used to prepare incoming `Data` for serialization.
var dataPreprocessor: DataPreprocessor { get }
/// `HTTPMethod`s for which empty response bodies are considered appropriate.
var emptyRequestMethods: Set<HTTPMethod> { get }
/// HTTP response codes for which empty response bodies are considered appropriate.
var emptyResponseCodes: Set<Int> { get }
func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> SerializedObject
func serializeDownload(request: URLRequest?,
response: HTTPURLResponse?,
fileURL: URL?,
error: Error?) throws -> SerializedObject
}
默认情况下,serializeDownload
方法是通过从磁盘读取下载的数据并调用 serialize
来实现的。因此,使用上面提到的 DownloadRequest
的响应 response(queue:completionHandler:)
方法实现对大型下载的自定义处理可能更为合适。
ResponseSerializer
为 dataPreprocessor
、emptyResponseMethods
和 emptyResponseCodes
提供了各种默认实现,这些实现可以在自定义类型中进行定制,如 Alamofire 附带的各种 ResponseSerializer
。
所有 ResponseSerializer
的使用都通过 DataRequest
和 DownloadRequest
上的方法进行:
// DataRequest
func response<Serializer: DataResponseSerializerProtocol>(
queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void) -> Self
// DownloadRequest
func response<Serializer: DownloadResponseSerializerProtocol>(
queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDownloadResponse<Serializer.SerializedObject>) -> Void) -> Self
Alamofire 包括几个常见的响应 handlers,包括:
responseData(queue:completionHandler)
:使用DataResponseSerializer
验证和预处理响应Data
。responseString(queue:encoding:completionHandler:)
:使用提供的String.Encoding
将响应Data
解析为String
。responseJSON(queue:options:completionHandler)
:使用提供的JSONSerialization.ReadingOptions
使用JSONSerialization
解析响应Data
。不建议使用此方法,仅为与现有的 Alamofire 用法兼容而提供。相反,应该使用responseDecodable
。responseDecodable(of:queue:decoder:completionHandler:)
:使用提供的DataDecoder
将响应Data
解析为提供的或推断的Decodable
类型。默认情况下使用JSONDecoder
。JSON 和泛型响应解析推荐用此方法。
在 DataRequest
或 DownloadRequest
上调用 responseData(queue:completionHandler:)
使用 DataResponseSerializer
验证 Data
是否已正确返回(除非 emptyResponseMethods
和 emptyResponseCodes
允许,否则不允许空响应),并将该 Data
传递到 dataPreprocessor
。此响应 handler 对于自定义 Data
处理非常有用,但通常不是必需的。
对 DataRequest
或 DownloadRequest
调用 responseString(queue:encoding:completionHa
使用 StringResponseSerializer
验证 Data
是否已正确返回(除非 emptyResponseMethods
和 emptyResponseCodes
允许,否则不允许空响应)并将该 Data
传递到 dataPreprocessor
。然后,使用从 HTTPURLResponse
解析的 String.Encoding
处理 Data
,并初始化一个 String
。
在 DataRequest
或 DownloadRequest
上调用 responseJSON(queue:options:completionHandler)
使用 JSONResponseSerializer
验证 Data
是否已正确返回(除非 emptyResponseMethods
和 emptyResponseCodes
允许,否则不允许空响应),并将该 Data
传递给 dataPreprocessor
。然后,使用提供的选项把预处理的 Data
传递给 JSONSerialization.jsonObject(with:options:)
。不再推荐使用此序列化程序。相反,使用 DecodableResponseSerializer
提供了更好的快速体验。
在 DataRequest
或 DownloadRequest
上调用 responseDecodable(of:queue:decoder:completionHandler)
使用 DecodableResponseSerializer
来验证 Data
是否已正确返回(除非 emptyResponseMethods
和 emptyResponseCodes
允许,否则不允许空响应),并将该 Data
传递给 dataPreprocessor
。然后,预处理的 Data
传递给提供的 DataDecoder
,并解析为提供的或推断的 Decodable
类型。
除了包含在 Alamofire 中的灵活的 ResponseSerializer
之外,还有其他定制响应处理的方法。
使用现有的 ResponseSerializer
然后转换输出是定制响应 handler 的最简单方法之一。DataResponse
和 DownloadResponse
都有 map
、tryMap
、mapError
和 tryMapError
方法,这些方法可以转换响应,同时保留与响应相关联的元数据。例如,可以使用 map
从可解码响应中提取属性,同时还保留以前的任何解析错误。
AF.request(...).responseDecodable(of: SomeType.self) { response in
let propertyResponse = response.map { $0.someProperty }
debugPrint(propertyResponse)
}
引发错误的转换也可以与 tryMap
一起使用,可能用于执行验证:
AF.request(..).responseDecodable(of: SomeType.self) { response in
let propertyResponse = response.tryMap { try $0.someProperty.validated() }
debugPrint(propertyResponse)
}
当 Alamofire 提供的 ResponseSerializer
或响应转换不够灵活,或者定制量很大时,创建 ResponseSerializer
是封装该逻辑的好方法。集成自定义 ResponseSerializer
通常有两个部分:创建遵循协议的类型和扩展相关请求类型以方便使用。例如,如果服务器返回了一个特殊编码的 String
(可能是用逗号分隔的值),那么这种格式的 ResponseSerializer
可能如下所示:
struct CommaDelimitedSerializer: ResponseSerializer {
func serialize(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?
) throws -> [String] {
// Call the existing StringResponseSerializer to get many behaviors automatically.
let string = try StringResponseSerializer().serialize(
request: request,
response: response,
data: data,
error: error
)
return Array(string.split(separator: ","))
}
}
请注意,serialize
方法的返回类型要满足 SerializedObject
associatedtype
要求。在更复杂的序列化器中,此返回类型本身可以是泛型的,从而允许泛型类型的序列化,如 DecodableResponseSerializer
所示。
为了使 CommaDelimitedSerializer
更有用,可以添加其他行为,比如允许通过将空 HTTP 方法和响应代码传递给底层的 StringResponseSerializer
来自定义它们。
NetworkReachabilityManager
监听移动网络和 WiFi
网络接口的主机和地址的可达性变化。
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.startListening { status in
print("Network Status Changed: \(status)")
}
一定要记住保存
manager
,否则不会报告状态更改。另外,不要将 scheme 包含在host
字符串中,否则可达性将无法正常工作。
当使用网络可达性来确定下一步要做什么时,需要记住一些重要的事情。
- 不要使用可达性来确定是否应发送网络请求。
- 你应该总是把请求发出去。
- 恢复可访问性后,使用事件重试失败的网络请求。
- 尽管网络请求可能仍然失败,但现在是重试请求的好时机。
- 网络可达性状态可用于确定网络请求失败的原因。
- 如果网络请求失败,则更有用的方法是告诉用户网络请求由于离线而失败,而不是更技术性的错误,例如“请求超时”
或者,使用 RequestRetrier
(如内置的 RetryPolicy
)可能会更简单、更可靠,而不是使用可访问性更新来重试因网络故障而失败的请求。默认情况下,RetryPolicy
将在各种错误条件下重试等幂请求,包括离线的网络连接。