Alamofire高级用法

高级用法

Alamofire建立在URLSession和基础URL加载系统之上。为了充分利用此框架,建议您熟悉基础网络堆栈的概念和功能。

推荐读物

Session

Alamofire的Session职责与其维护的URLSession实例大致相同:它提供API以产生封装不同URLSessionTask子类的各种Request子类,以及封装应用于实例所产生的所有Request的各种配置。

Session提供一个default单例实例,该实例为AFenum名称空间中的顶级API 提供动力。因此,以下两个语句是等效的:

AF.request("https://httpbin.org/get")
let session = Session.default
session.request("https://httpbin.org/get")

创建自定义Session实例

大多数应用程序将需要以各种方式来自定义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行为。

URLSessionConfiguration创建一个Session

为了自定义基础URLSession行为,可以提供自定义URLSessionConfiguration实例。推荐从URLSessionConfiguration.af.default实例开始,因为它增加了由Alamofire提供的默认Accept-EncodingAccept-Language以及User-Agent头文件,但任何URLSessionConfiguration都可以使用。

let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false

let session = Session(configuration: configuration)

推荐在URLSessionConfiguration设置AuthorizationContent-Type头。而是使用提供的headersAPI ,ParameterEncoder,或RequestAdapter,将它们添加到Request中。

正如Apple在其文档中所指出的那样,将实例添加到中URLSession(或在Alamofire的情况下,用于初始化一个 Session)之后对URLSessionConfiguration属性进行更改无效。

SessionDelegate

一个SessionDelegate实例封装不同的URLSessionDelegate所有操作和相关的协议回调。SessionDelegate还为Alamofire产生的每个Request充当SessionStateDelegate角色,从而允许Request间接导入由Session实例创建的状态。SessionDelegate可以被自定义以特定FileManager实例,该实例将用于任何磁盘访问,例如访问UploadRequests 上传的文件或DownloadRequests 下载的文件。

let delegate = SessionDelegate(fileManager: .default)

startRequestsImmediately

默认情况下,一旦添加了至少一个响应处理,Session它将立即在一个Request上面调用resume()。设置startRequestsImmediatelyfalse需要所有Request都手动调用resume()

let session = Session(startRequestsImmediately: false)

一个SessionDispatchQueue

默认情况下,Session实例为所有异步工作使用单个DispatchQueue实例。这包括underlyingQueueURLSessiondelegate OperationQueue),为所有的URLRequest创建,所有响应系列化工作,所有内部SessionRequest状态突变。如果围绕URLRequest创建或响应序列化(response serialization),性能分析显示了特定瓶颈,则可以为每个工作领域提供单独的DispatchQueueSession

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必须是串行队列中,但requestQueueserializationQueue可以是串行或并行的队列。建议使用串行队列作为默认队列,除非性能分析显示工作被延迟(在这种情况下,使队列并行可以提高整体性能)。

添加一个 RequestInterceptor

Alamofire的RequestInterceptor协议(RequestAdapter & RequestRetrier)提供了重要而强大的请求自适应和重试功能。它可以同时在SessionRequest级别应用。有关RequestInterceptorAlamofire的详细信息以及各种实现RetryPolicy,请参见下文

let policy = RetryPolicy()
let session = Session(interceptor: policy) 

添加一个 ServerTrustManager

Alamofire的ServerTrustManager类封装了域和ServerTrustEvaluating-conforming类型的实例之间的映射,从而提供了自定义TLS安全性Session处理的功能。这包括证书的使用和公钥固定以及证书吊销检查。有关更多信息,请参见关于ServerTrustManager和的部分ServerTrustEvaluating。初始化一个 ServerTrustManger就像提供域和要执行的评估类型之间的映射一样简单:

let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let session = Session(serverTrustManager: manager)

有关评估服务器信任的更多详细信息,请参阅下面的详细文档。

添加一个 RedirectHandler

Alamofire的RedirectHandler协议可自定义HTTP重定向响应的处理。它可以同时在SessionRequest级别应用。Alamofire包括遵循RedirectHandlerRedirector类型,并提供对重定向的简单控制。有关更多详细信息RedirectHandler,请参见下面的详细文档。

let redirector = Redirector(behavior: .follow)
let session = Session(redirectHandler: redirector)

添加一个 CachedResponseHandler

Alamofire的CachedResponseHandler协议可自定义响应的缓存,并且可以在SessionRequest级别应用。Alamofire包括遵循CachedResponseHandlerResponseCacher类型,并提供对响应缓存的简单控制的。有关更多详细信息,请参见下面的详细文档。

let cacher = ResponseCacher(behavior: .cache)
let session = Session(cachedResponseHandler: cacher)

添加EventMonitor

Alamofire的EventMonitor协议可提供有关Alamofire内部事件的强大见解。它可用于提供日志记录和其他基于事件的功能。在初始化时Session接受一个EventMonitor-conforming实例的数组。

let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError = { (request, task, error) in
    debugPrint(request)
}
let session = Session(eventMonitors: [monitor])

URLSession创建实例

除了前面提到的convenience初始化器外,Session还可以直接从URLSession 初始化。但是,使用此初始化器时要牢记一些要求,因此建议使用便捷初始化器。这些包括:

  • Alamofire不支持URLSession配置用于后台使用。因为Session初始化时,这将导致运行时错误。
  • 一个SessionDelegate实例必须被创建和被用为URLSessiondelegate,以及传递给Session初始化器。
  • 自定义OperationQueue必须被传递为URLSessiondelegateQueue。该队列必须是一个串行队列,它必须有一个后盾DispatchQueue,而且DispatchQueue必须传递给Session它的rootQueue
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执行的每个请求是由特定的类封装DataRequestUploadRequestDownloadRequest。每个这些类的封装功能对每个类型的请求唯一,但DataRequestDownloadRequest继承自共同父类,RequestUploadRequest继承自DataRequest)。Request实例从不直接被创建,而是通过各种request方法之一从Session实例出售。

请求管道

一旦Request使用其初始参数或URLRequestConvertible值创建了一个子类,它将通过构成Alamofire请求管道的一系列步骤传递。对于成功的请求,这些包括:

  1. 初始参数(例如HTTP方法,头和参数)被封装到内部URLRequestConvertible值中。如果URLRequestConvertible直接传递值,则该值将不变使用。
  2. asURLRequest()URLRequestConvertible值上调用,创建第一个URLRequest值。此值传递到Request并存储在requests中。如果URLRequestConvertible值是从传递给Session方法的参数被创建,则在URLRequest被创建时,任何提供的RequestModifier将被调用。
  3. 如果有任何SessionRequest RequestAdapterRequestInterceptor,他们用以前创建的URLRequest来调用。适配的URLRequest然后将被传递到Request并存储在request中。
  4. Session调用Request来创建URLSessionTask,以执行基于URLRequest上面的网路请求。
  5. 一旦URLSessionTask完成,URLSessionTaskMetrics已经采集,Request执行它的Validator
  6. Request执行任何响应处理,例如已附加的responseDecodable

在这些步骤中的任何一个步骤中,都可以通过创建或接收的Error值来指示失败,然后将其传递给关联的Request。例如,除了步骤1和4外,上述所有步骤都可以创建一个Error,然后将其传递到响应处理或可重试。以下是在整个Request管道中可能失败或不失败的一些示例。

  1. 参数封装不会失败。
  2. 调用asURLRequest()时,任何URLRequestConvertible值都会产生错误。这允许各种URLRequest属性的初始验证或参数编码的失败。
  3. RequestAdapter可能在适应期间失败,可能是由于缺少授权令牌。
  4. URLSessionTask 创建不会失败。
  5. URLSessionTask由于各种原因(包括网络可用性和取消)可能会出现错误。这些Error值将传递回Request
  6. 响应处理可以产生任何Error,通常是由于无效的响应或其他解析错误所致。

一旦将错误传递给RequestRequest将会尝试运行与SessionRequest关联的任何RequestRetrier。如果RequestRetrier选择重试Request,则再次运行完整管道。RequestRetrier也可以产生Error,而不会触发重试。

Request

尽管Request不封装任何特定类型的请求,但它包含Alamofire执行的所有请求共有的状态和功能。这包括:

状态

所有Request类型都包含状态的概念,指示状态下Request生命中的主要事件。

public enum State {
    case initialized
    case resumed
    case suspended
    case cancelled
    case finished
}

Request在创建后开始进入.initialized状态。Request可以通过调用适当的生命周期方法来暂停,恢复和取消。

  • resume()恢复或启动Request的网络流量。如果startRequestsImmediatelytrue,则将响应处理程序添加到中后,将自动调用此方法Request
  • suspend()暂停或暂停Request和及其网络流量。Request可以恢复此状态下的,但是只能DownloadRequests继续传输数据。其他Request将重新开始。
  • cancel()取消Request。一旦处于此状态,a Request将无法恢复或暂停。当cancel()被调用时,Requesterror属性将与设置AFError.explicitlyCancelled实例。如果a Request恢复并且以后不取消,则在.finished所有响应验证器和响应序列化程序运行之后,它将达到状态。但是,如果Request在达到.finished状态后将附加的响应序列化器添加到该状态,它将转换回该.resumed状态并再次执行网络请求。
进度

为了跟踪请求的进度,同时Request提供 uploadProgressdownloadProgress属性以及基于闭包的uploadProgressdownloadProgress方法。像所有基于闭包的RequestAPI一样,进度API可以Request与其他方法链接在一起。与其他基于闭包的API一样,也应_在_添加任何响应处理程序(如)_之前_将它们添加到请求中responseDecodable

AF.request(...)
    .uploadProgress { progress in
        print(progress)
    }
    .downloadProgress { progress in
        print(progress)
    }
    .responseDecodable(of: SomeType.self) { response in
        debugPrint(response)
    }

重要的是,并非所有Request子类都能够准确报告其进度,或者可能有其他依赖项来报告。

  • 对于上传进度,可以通过以下方式确定进度:
    • Data提供给的上载主体的对象的长度UploadRequest
    • 由磁盘上作为的上载主体提供的文件的长度UploadRequest
    • 通过Content-Length请求上标头的值(如果已手动设置)。
  • 对于下载进度,有一个要求:
    • 服务器响应必须包含Content-Length标头。不幸的是,对于进度报告可能还有其他未记录的要求,URLSession因此无法进行准确的进度报告。
处理重定向

Alamofire的RedirectHandler协议可为的重定向处理提供控制和自定义Request。除了per-Session RedirectHandler,每个人Request都可以指定自己的名字,RedirectHandler该名字优先于Session

let redirector = Redirector(behavior: .follow)
AF.request(...)
    .redirect(using: redirector)
    .responseDecodable(of: SomeType.self) { response in 
        debugPrint(response)
    }

注意:只能RedirectHandler在上设置一个Request。尝试设置多个将导致运行时异常。

自定义缓存

Alamofire的CachedResponseHandler协议提供了对响应缓存的控制和自定义。除了per-Session CachedResponseHandler,每个人Request都可以指定自己的名字,CachedResponseHandler该名字优先于Session

let cacher = ResponseCacher(behavior: .cache)
AF.request(...)
    .cacheResponse(using: cacher)
    .responseDecodable(of: SomeType.self) { response in 
        debugPrint(response)
    }

注意:只能CachedResponseHandler在上设置一个Request。尝试设置多个将导致运行时异常。

证书

为了利用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所有请求都包含标头,则应将其直接提供(作为的一部分Request)或通过提供RequestInterceptor

此外,添加原始文件URLCredential也很容易:

let credential = URLCredential(...)
AF.request(...)
    .authenticate(using: credential)
    .responseDecodable(of: SomeType.self) { response in 
        debugPrint(response)
    }
一个RequestURLRequest

由一个Request发出的每个网络请求最终都封装在一个URLRequest值中,该值是根据传递给Session请求方法之一的各种参数创建的。Request会将这些URLRequest的副本保留在其requestsarray属性中。这些值包括根据传递的参数创建的初始URLRequest以及由RequestInterceptor 创建的任何URLRequest。但是,该数组不包括由代表Request发出的由URLSessionTask执行的URLRequest。要检查这些值,该tasks属性可以访问由Request执行的所有URLSessionTasks

URLSessionTask

在许多方面,各种Request子类都充当的包装器URLSessionTask,提供用于与特定类型任务交互的特定API。这些任务Request通过tasksarray属性在实例上可见。这包括为创建的初始任务Request以及作为重试过程的一部分创建的任何后续任务,每个重试都有一个任务。

响应

请求完成后,每一个都Request可能具有HTTPURLResponse可用的值。仅当未取消请求且没有失败发出网络请求时,此值才可用。此外,如果重试该请求,则只有_最后一个_响应可用。中间响应可以从属性中的URLSessionTasks 派生tasks

URLSessionTaskMetrics

Alamofire会URLSessionTaskMetrics为每场URLSessionTask表演收集值Request。这些值在metrics属性中可用,每个值对应于URLSessionTaskin tasks中的相同索引。

URLSessionTaskMetrics也可用于Alamofire的各种响应类型,例如DataResponse。例如:

AF.request(...)
    .responseDecodable(of: SomeType.self) { response in {
        print(response.metrics)
    }

由于FB7624529URLSessionTaskMetrics当前禁止在watchOS上进行收集。

DataRequest

DataRequestRequest子类,它封装一个URLSessionDataTask下载服务器响应到内存中的Data。因此,认识到下载量过大可能会对系统性能产生不利影响是重要的。对于那些类型的下载,建议使用DownloadRequest将数据保存到磁盘。

附加状态

DataRequest除了Request所提供的属性外,还具有一些属性。这些包括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
        ...
    }

DataStreamRequest

DataStreamRequestRequest一个子类,它封装一个 URLSessionDataTask和来自一个HTTP连接超时的流Data

附加状态

DataStreamRequest 不包含任何其他公共状态。

验证方式

DataStreamRequests默认情况下不验证响应。相反,validate()必须将调用添加到请求中,以验证各种属性是否有效。

public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> Result<Void, Error>

默认情况下,添加validate()可确保响应状态代码在该200..<300范围内,并且响应Content-TypeAccept值与请求的值匹配。可以通过传递Validation闭包来进一步自定义验证:

AF.request(...)
    .validate { request, response in
        ...
    }

UploadRequest

UploadRequest是的子类,DataRequest它封装URLSessionUploadTask,将Data值,文件上传到磁盘或InputStream远程服务器。

附加状态

UploadRequest除了所提供的属性外,还具有一些属性DataRequest。其中包括一个FileManager实例,该实例用于自定义在上载文件时对磁盘的访问,并且upload,它封装了URLRequestConvertible用于描述请求的值以及Uploadable,它确定了要执行的上载类型。

DownloadRequest

DownloadRequest是一个具体的子类,Request其中封装了对磁盘的URLSessionDownloadTask下载响应Data

附加状态

DownloadRequest除了所提供的属性外,还具有一些属性Request。这些措施包括resumeData,将Data产生取消时DownloadRequest,可用于后继续下载,并且fileURL,将URL下载的文件可一旦下载完成,在这。

取消

除了支持所cancel()提供的方法外RequestDownloadRequest还包括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?)

不能Data直接访问下载的文件,而必须使用fileURL提供的文件进行访问。否则,DownloadRequest的验证器的功能与的相同DataRequest

使用RequestInterceptor适应和重试请求

Alamofire的RequestInterceptor协议(RequestAdapterRequestRetrier协议组成)激活强大的完全Session和完全Request能力。这些包括身份验证系统,在该系统中,将通用头添加到每个Request,并Request在授权到期时重试。此外,Alamofire包括一个内置RetryPolicy类型,当由于各种常见的网络错误而导致请求失败时,它可以轻松重试。

RequestAdapter

Alamofire的 RequestAdapter协议允许每个由Session执行的URLRequest操作都经过检查和转变,然后才能通过网络发布。适配器的一种非常常见的用法是在某种类型的身份验证后面向请求添加Authorization头。

RequestAdapter协议仅有一必须条件:

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

其参数包括:

  • urlRequest:由参数或用于创建RequestURLRequestConvertible值最初创建的URLRequest
  • session:这里的Session,创建了Request,为它调用适配器。
  • completion必须 调用该异步completion回调来指示适配器已完成的。它具有异步特性,在通过网络发送Request之前,能够使RequestAdapter从网络或磁盘访问异步资源。提供给completion闭包的Result可以返回带有修改后的URLRequest的一个值.success,或一个值.failure具有相关联的Error,这将随后被用于失败Request。例如,添加Authorization头需要修改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(bearerToken: accessToken))

    completion(.success(urlRequest))
}

RequestRetrier

Alamofire的RequestRetrier协议允许Request执行中遇到的Error时进行重试。这包括在Alamofire 请求管道的任何阶段生成的Error

RequestRetrier协议仅有一必须条件。

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

其参数包括:

  • request:遇到错误的Request
  • session:管理RequestSession
  • error:触发了重试尝试的Error。通常是AFError
  • completion必须 调用异步completion回调来指示Request是否应重试的。必须使用RetryResult来调用它。

RetryResult类型表示在RequestRetrier中实现的任何逻辑的结果。定义为:

///确定是否需要重试的结果。
public enum RetryResult {
     ///应立即尝试重试。
    case retry 
    ///应该在关联的`TimeInterval`之后尝试重试。
    case retryWithDelay(TimeInterval)
     ///不重试。
    case doNotRetry
     ///由于相关的“错误”,请勿重试。
    case doNotRetryWithError(Error)
}

例如,如果请求是幂等的,则Alamofire的RetryPolicy类型将自动重试由于某种网络错误而失败的Request

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

使用多个RequestInterceptor

Alamofire支持使用多的RequestInterceptor,在两者SessionRequest水平上,通过使用Interceptor型。Interceptor可以由适配器和重试器闭包组成,单个RequestAdapterRequestRetrier的组合,或RequestAdapterRequestRetrier,和RequestInterceptor数组的组合。

let adapter = // Some RequestAdapter
let retrier = // Some RequestRetrier
let interceptor = // Some RequestInterceptor

let adapterAndRetrier = Interceptor(adapter: adapter, retrier: retrier)
let composite = Interceptor(interceptors: [adapterAndRetrier, interceptor])

当由多个RequestAdapters 组成时,Interceptor将依次调用每个RequestAdapter。如果它们全部成功,则将使用RequestAdapter链中的最后一个URLRequest执行请求。如果失败,则适配将停止,并且Request失败并返回错误。同样,当由多个RequestRetrier 组成时,重试的执行顺序与将重试器添加到实例的顺序相同,直到它们全部完成或其中一个因错误而失败。

AuthenticationInterceptor

Alamofire的AuthenticationInterceptor类是一个旨在处理身份验证请求所涉及的队列和线程复杂性的RequestInterceptor。它利用注入的Authenticator协议来管理匹配AuthenticationCredential的生命周期。这是一个简单的示例,说明如何与OAuthCredential一起实现OAuthAuthenticator类。

OAuthCredential

struct OAuthCredential: AuthenticationCredential {
    let accessToken: String
    let refreshToken: String
    let userID: String
    let expiration: Date

    //如果在过期5分钟内需要刷新,则
    var requiresRefresh: Bool { Date(timeIntervalSinceNow: 60 * 5) > expiration }
}

OAuthAuthenticator

class OAuthAuthenticator: Authenticator {
    func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
        urlRequest.headers.add(.authorization(bearerToken: credential.accessToken))
    }

  func refresh(_ credential: OAuthCredential,
                 for session: Session,
                 completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
         //使用refresh token刷新credential...然后使用新credential调用completion。
        //新的credential将自动存储在“ AuthenticationInterceptor”中。将来的请求将
        //使用apply(_:to:)方法使用新credential进行身份验证。
    }

 func didRequest(_ urlRequest: URLRequest,
                    with response: HTTPURLResponse,
                    failDueToAuthenticationError error: Error) -> Bool {
         //如果身份验证服务器无法使credentials无效,则返回`false` 
        return false

        //如果身份验证服务器可以使凭据无效,则检查响应与
        //身份验证服务器作为身份验证失败返回的
        //内容相是否匹配。通常是带有自定义header值的一个401。
        //返回response.statusCode == 401
    }

    func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
         //如果验证服务器无法使凭证无效,则返回 true 
        return true

        //如果验证服务器可以使凭据无效,则将
        // URLRequest中的
        //“ Authorization”头值与使用“ Credential”访问令牌生成的Bearer令牌进行比较。
        // let bearerToken = HTTPHeader.authorization(bearerToken: credential.accessToken).value
        // return urlRequest.headers["Authorization"] == bearerToken
    }
}

用法

//通常加载 从钥匙串如果它存在 
let credential = OAuthCredential(accessToken: "a0",
                                 refreshToken: "r0",
                                 userID: "u0",
                                 expiration: Date(timeIntervalSinceNow: 60 * 60))

// 创建拦截器
let authenticator = OAuthAuthenticator()
let interceptor = AuthenticationInterceptor(authenticator: authenticator,
                                            credential: credential)

// 使用拦截器执行请求
let session = Session()
let urlRequest = URLRequest(url: URL(string: "https://api.example.com/example/user")!)
session.request(urlRequest, interceptor: interceptor)

安全

与服务器和Web服务进行通信时使用安全的HTTPS连接是保护敏感数据的重要步骤。默认情况下,Alamofire收到与相同的自动TLS证书和证书链验证URLSession。虽然这可以确保证书链有效,但不能防止中间人(MITM)攻击或其他潜在漏洞。为了减轻MITM攻击,处理敏感客户数据或财务信息的应用程序应使用Alamofire ServerTrustEvaluating协议提供的证书或公钥固定。

使用ServerTrustManager和评估服务器信任ServerTrustEvaluating

ServerTrustEvaluating

ServerTrustEvaluating协议提供了一种执行任何类型的服务器信任评估的方法。它有一个要求:

func evaluate(_ trust: SecTrust, forHost host: String) throws

该方法提供了从底层获得的SecTrust价值和主体StringURLSession并提供了进行各种评估的机会。

Alamofire包括许多不同类型的信任评估器,可对评估过程提供可组合的控制:

  • DefaultTrustEvaluator:使用默认的服务器信任评估,同时允许您控制是否验证质询提供的主机。
  • RevocationTrustEvaluator:检查收到的证书的状态,以确保尚未被吊销。由于它需要网络请求,因此通常不会在每个请求上执行此操作。
  • PinnedCertificatesTrustEvaluator:使用提供的证书来验证服务器信任。如果固定证书之一与服务器证书之一匹配,则服务器信任被视为有效。该评估者还可以接受自签名证书。
  • PublicKeysTrustEvaluator:使用提供的公共密钥来验证服务器信任。如果固定的公用密钥之一与服务器证书公用密钥之一匹配,则认为服务器信任有效。
  • CompositeTrustEvaluator:计算ServerTrustEvaluating值的数组,只有在所有值都成功时才成功。此类型可用于组合例如RevocationTrustEvaluatorPinnedCertificatesTrustEvaluator
  • DisabledTrustEvaluator注意:此评估程序仅应在调试方案中使用,因为它会禁用所有评估,这反过来将始终将任何服务器信任视为有效。切勿在生产环境中使用该评估器!
ServerTrustManager

ServerTrustManager负责存储的内部映射ServerTrustEvaluating值到特定的主机。这使Alamofire可以使用不同的评估程序评估每个主机。

let evaluators: [String: ServerTrustEvaluating] = [
     //默认情况下,应用程序捆绑包中包含的证书会自动固定。
    "cert.example.com": PinnedCertificatesTrustEvaluator(),
     //默认情况下,会自动使用应用程序捆绑包中包含的证书的公钥。
    "keys.example.com": PublicKeysTrustEvaluator(),
]

let manager = ServerTrustManager(evaluators: serverTrustPolicies)

ServerTrustManager将具有以下行为:

  • cert.example.com 将始终使用默认情况下启用证书锁定并启用主机验证的证书固定,因此需要满足以下条件才能使TLS握手成功:
    • 证书链_必须_有效。
    • 证书链_必须_包含固定证书之一。
    • 质询主机_必须_与证书链的叶证书中的主机匹配。
  • keys.example.com 将始终使用默认设置并启用主机验证的公钥固定,因此需要满足以下条件才能使TLS握手成功:
    • 证书链_必须_有效。
    • 证书链_必须_包含固定的公共密钥之一。
    • 质询主机_必须_与证书链的叶证书中的主机匹配。
  • 对其他主机的请求将产生错误,因为ServerTrustManager默认情况下要求对所有主机进行评估。
子类化服务器信任策略管理器

如果您发现自己需要更灵活的服务器信任策略匹配行为(即通配符域),则使用子类ServerTrustManagerserverTrustEvaluator(forHost:)使用您自己的自定义实现覆盖该方法。

final class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
    override func serverTrustEvaluator(forHost host: String) -> ServerTrustEvaluating? {
        var policy: ServerTrustPolicy?

        //实现您的自定义域匹配行为...

        return policy
    }
}

应用程式传输安全性

通过在iOS 9中添加应用传输安全性(ATS),有可能对ServerTrustManager多个ServerTrustEvaluating对象使用自定义设置无效。如果持续CFNetwork SSLHandshake failed (-9806)出现错误,则可能是您遇到了此问题。除非您在应用程序的plist中配置ATS设置以禁用足够多的ATS设置以允许您的应用程序评估服务器信任度,否则Apple的ATS系统将覆盖整个挑战系统。如果您遇到此问题(使用自签名证书的可能性很高),则可以通过在上添加NSAppTransportSecurity替代来解决此问题Info.plist。您可以使用该nscurl工具的--ats-diagnostics选项对主机执行一系列测试,以查看可能需要哪些ATS替代。

通过本地网络使用自签名证书

如果您尝试连接到在本地主机上运行的服务器,并且使用的是自签名证书,则需要将以下内容添加到中Info.plist

<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsLocalNetworking</key>
        <true/>
    </dict>
</dict>

根据Apple文档,将设置NSAllowsLocalNetworkingYES允许加载本地资源,而无需为应用程序的其余部分禁用ATS。

自定义缓存和重定向处理

URLSession允许使用URLSessionDataDelegateURLSessionTaskDelegate方法自定义缓存和重定向行为。Alamofire将这些自定义点显示为CachedResponseHandlerRedirectHandler协议。

CachedResponseHandler

CachedResponseHandler协议允许控制将HTTP响应缓存到URLCacheSession发出请求关联的实例中。该协议有一个单一要求:

func dataTask(_ task: URLSessionDataTask,
              willCacheResponse response: CachedURLResponse,
              completion: @escaping (CachedURLResponse?) -> Void)

从方法签名中可以看出,此控件仅适用于Request使用基础URLSessionDataTask进行网络传输的,其中包括DataRequests和UploadRequests(因为URLSessionUploadTask是的子类URLSessionDataTask)。考虑将响应用于缓存的条件非常广泛,因此最好查看该URLSessionDataDelegate方法的文档urlSession(_:dataTask:willCacheResponse:completionHandler:)。一旦考虑将响应用于缓存,便可以进行多种有价值的操作:

  • 通过返回防止完全缓存响应nil CachedURLResponse
  • 修改CachedURLResponsestoragePolicy改变,其中的缓存值应该住。
  • URLResponse直接修改基础,添加或删除值。
  • 修改Data与响应关联的关联(如果有)。

Alamofire包含ResponseCacher符合的类型CachedResponseHandler,从而易于缓存,而不是缓存或修改响应。ResponseCacher取一个Behavior值来控制缓存行为。

public enum Behavior {
     ///将缓存的响应存储在缓存中。
    case cache
     ///防止将缓存的响应存储在缓存中。
    case doNotCache
     ///修改缓存的响应,然后再将其存储在缓存中。
    case modify((URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)
}

ResponseCacher可以同时使用a SessionRequest基础,如上所述。

RedirectHandler

RedirectHandler协议允许控制特定Requests 的重定向行为。它有一个要求:

func task(_ task: URLSessionTask,
          willBeRedirectedTo request: URLRequest,
          for response: HTTPURLResponse,
          completion: @escaping (URLRequest?) -> Void)

此方法提供了修改重定向URLRequest或通过nil以完全禁用重定向的机会。Alamofire提供了Redirector符合的类型RedirectHandler,从而易于遵循,不遵循或修改重定向的请求。Redirector采用一个Behavior值来控制重定向行为。

public enum Behavior {
     ///按照响应中定义的重定向。
    case follow
     不要遵循响应中定义的重定向。
    case doNotFollow
     ///修改响应中定义的重定向请求。
    case modify((URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?)
}

Redirector可以同时使用a SessionRequest基础,如上所述。

使用EventMonitors

EventMonitor协议允许观察和检查大量内部Alamofire事件。其中包括Alamofire实施的所有URLSessionDelegateURLSessionTaskDelegateURLSessionDownloadDelegate方法,以及大量内部Request事件。除了这些事件(默认情况下是不起作用的空方法)以外,EventMonitor协议还要求DispatchQueue在其上调度所有事件以保持性能。此DispatchQueue默认为.main,而是专用串行队列被推荐用于任何自定义符合类型。

Logging

EventMonitor协议的最大用途也许是实现相关事件的记录。一个简单的实现可能看起来像这样:

final class Logger: EventMonitor {
    let queue = DispatchQueue(label: ...)
    
    //恢复任何类型的Request时调用的事件。
    func requestDidResume(_ request: Request) {
        print("Resuming: \(request)")
    }
    
    //每当DataRequest解析响应时调用的事件。
    func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
        debugPrint("Finished: \(response)")
    }
}

Logger可以使用Session与上述相同的方式将这种类型添加到中:

let logger = Logger()
let session = Session(eventMonitors: [logger])

发出请求

作为框架,Alamofire有两个主要目标:

  1. 为了轻松实现对原型和工具的网络请求
  2. 充当应用程序网络的通用基础

它通过使用功能强大的抽象,提供有用的默认值以及包含常见任务的实现来实现这些目标。但是,一旦对Alamofire的使用超出了一些要求,就必须超越高级的默认实现,而转向为特定应用程序定制的行为。Alamofire提供URLConvertibleURLRequestConvertible协议来帮助进行这种自定义。

URLConvertible

URLConvertible可以将采用协议的类型用于构造URL,然后将其用于内部构造URL请求。StringURL以及URLComponents符合URLConvertible在默认情况下,允许任何人将其交给url参数的requestuploaddownload方法:

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

采用该URLRequestConvertible协议的类型可用于构造URLRequestURLRequest符合URLRequestConvertible默认情况下,允许它被传递到requestuploaddownload直接的方法。Alamofire URLRequestConvertible用作流经请求管道的所有请求的基础。URLRequest建议直接使用s在Alamofire提供URLRequestParameterEncoders 之外自定义创建的推荐方法。

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 URLConvertibleURLRequestConvertible协议以及Router设计模式可为您提供帮助。

“路由器”是一种定义“路由”或请求组成部分的类型。这些组件可以包括的组成部分,URLRequest发出请求所需的参数以及各种按请求的Alamofire设置。一个简单的路由器可能看起来像这样:

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)

更复杂的路由器可能包括请求的参数。使用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
    }
}

可以将路由器扩展到具有任意数量的可配置属性的任意数量的端点,但是一旦达到一定程度的复杂性,就应考虑将一台大型路由器分成较小的路由器以用于API的各个部分。

响应处理

Alamofire通过各种response方法和ResponseSerializer协议提供响应处理。

处理响应而不进行序列化

双方DataRequestDownloadRequest提供方法,使响应处理,而不调用任何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

与所有响应处理程序一样,所有序列化工作(在本例中为无)在内部队列上执行,并且完成处理程序在queue传递给方法时调用。这意味着main默认情况下不必分派回队列。但是,如果在完成处理程序中要执行任何重要的工作,则建议将自定义队列传递给响应方法,并main在必要时在处理程序本身中分派回去。

ResponseSerializer

ResponseSerializer协议由DataResponseSerializerProtocolDownloadResponseSerializerProtocol协议组成。合并后的版本ResponseSerializer如下所示:

public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol {
     ///要创建的序列化对象的类型。
    associatedtype SerializedObject

    /// DataPreprocessor用于准备传入的Data进行序列化。
    var dataPreprocessor: DataPreprocessor { get }
     /// “ HTTPMethod”认为适合使用空响应主体。
    var emptyRequestMethods: Set<HTTPMethod> { get }
     /// HTTP响应代码,认为空响应主体是合适的。
    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方法是通过Data从磁盘读取下载并调用serialize来实现的。因此,可能更适合实现自定义处理用于使用大型下载DownloadRequestresponse(queue:completionHandler:)上述方法。

ResponseSerializer提供了dataPreprocessoremptyResponseMethods和的各种默认实现,并且emptyResponseCodes可以按照一致的类型进行自定义,例如ResponseSerializerAlamofire附带的各种。

所有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包括一些常见的响应处理程序,包括:

  • responseData(queue:completionHandler):验证并预处理该响应Data使用DataResponseSerializer
  • responseString(queue:encoding:completionHandler:):使用提供的将响应解析Data为。String``String.Encoding
  • responseJSON(queue:options:completionHandler):解析响应Data使用JSONSerialization使用所提供的JSONSerialization.ReadingOptions。不建议使用此方法,仅是为了与现有Alamofire用法兼容。而是responseDecodable应使用。
  • responseDecodable(of:queue:decoder:completionHandler:):使用提供的将响应解析Data为提供的或推断的Decodable类型DataDecoder。使用JSONDecoder默认。JSON和通用响应解析的推荐方法。
DataResponseSerializer

呼叫responseData(queue:completionHandler:)DataRequestDownloadRequest使用DataResponseSerializer该验证Data(除非允许没有空的反应已被适当返回emptyResponseMethodsemptyResponseCodes),并把该Data经过dataPreprocessor。此响应处理程序对于自定义Data处理很有用,但通常不是必需的。

StringResponseSerializer

呼叫responseString(queue:encoding:completionHandler)DataRequestDownloadRequest使用StringResponseSerializer该验证Data(除非允许没有空的反应已被适当返回emptyResponseMethodsemptyResponseCodes),并把该Data经过dataPreprocessor。经预处理Data然后被用来初始化一个String使用String.Encoding从解析HTTPURLResponse

JSONResponseSerializer

呼叫responseJSON(queue:options:completionHandler)DataRequestDownloadRequest使用JSONResponseSerializer该验证Data(除非允许没有空的反应已被适当返回emptyResponseMethodsemptyResponseCodes),并把该Data经过dataPreprocessorData然后JSONSerialization.jsonObject(with:options:)使用提供的选项传递经过预处理的内容。不再建议使用此序列化程序。相反,使用DecodableResponseSerializer提供更好的Swift体验。

DecodableResponseSerializer

呼叫responseDecodable(of:queue:decoder:completionHandler)DataRequestDownloadRequest使用DecodableResponseSerializer该验证Data(除非允许没有空的反应已被适当返回emptyResponseMethodsemptyResponseCodes),并把该Data经过dataPreprocessorData然后将预处理的内容传递给提供的类型,DataDecoder然后解析为提供的或推断的Decodable类型。

自定义响应处理程序

除了ResponseSerializerAlamofire随附的flexible 之外,还有其他自定义响应处理的方法。

响应变换

使用现有的ResponseSerializer然后转换输出是定制响应处理程序的最简单方法之一。双方DataResponseDownloadResponsemaptryMapmapError,和tryMapError方法,同时保留与响应相关的元数据,可以将响应。例如,Decodable可以使用来从响应中提取属性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:创建符合类型并扩展相关Request类型以使其易于使用。例如,如果服务器返回经过特殊编码的String,也许用逗号分隔的值,则ResponseSerializer这种格式的格式可能如下所示:

struct CommaDelimitedSerializer: ResponseSerializer {
    func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> [String] {
         //调用现有的StringResponseSerializer以自动获得许多行为。
        let string = try StringResponseSerializer().serialize(request: request, 
                                                              response: response, 
                                                              data: data, 
                                                              error: error)
        
        return Array(string.split(separator: ","))
    }
}

请注意,方法SerializedObject associatedtype的返回类型可以满足要求serialize。在更复杂的序列化程序中,此返回类型本身可以是通用的,从而允许对通用类型进行序列化,如所示DecodableResponseSerializer

为了使它CommaDelimitedSerializer更有用,可以添加其他行为,例如允许通过将空HTTP方法和响应代码传递给底层来自定义空HTTP方法和响应代码StringResponseSerializer

流响应处理程序

DataStreamRequest使用其自身唯一的响应处理程序类型来处理Data作为流一部分的传入。除了提供的处理程序之外,还可以通过使用DataStreamSerializer协议来执行自定义序列化。

public protocol DataStreamSerializer {
     ///从序列化的Data产生的类型。
    associatedtype SerializedObject

    ///将传入的“数据”序列化为“ SerializedObject”值。
    /// 
    ///-参数数据:要序列化的数据。
    /// 
    ///-引发:序列化过程中产生的任何错误。
    func serialize(_ data: Data) throws -> SerializedObject
}

任何自定义DataStreamSerializer可被用于处理流Data通过使用responseStream方法:

AF.streamRequest(...).responseStream(using: CustomSerializer()) { stream in 
    // Process stream.
}

Alamofire包含DecodableStreamSerializerDataStreamSerializer可以解析Decodable传入的类型Data。可以使用DataDecoder实例和a 对其进行自定义,DataPreprocessor并通过以下responseStreamDecodable方法使用:

AF.streamRequest(...).responseDecodable(of: DecodableType.self) { stream in 
    // Process stream.
}

或者直接在前面提到的streamResponse方法中使用它:

AF.streamRequest(...).responseStream(using: DecodableStreamSerializer<DecodableType>(decoder: JSONDecoder())) { stream in 
    // Process stream.
}

将Alamofire与Combine结合使用

在支持Combine框架的系统上,Alamofire提供了使用自定义Publisher类型发布响应的功能。这些发布者的工作方式非常类似于Alamofire的响应处理程序。它们被链接到请求上,并且像响应处理程序一样,应该紧随其他API之后validate()。例如:

AF.request(...).publishDecodable(type: DecodableType.self)

此代码产生一个DataResponsePublisher<DecodableType>值,该值将发布一个DataResponse<DecodableType, AFError>值。像所有Alamofire一样Publisher,它DataResponsePublisher是完全惰性的,这意味着仅resume当下游Subscriber对值提出要求时,才添加响应处理程序和请求。它仅提供一个值,无法重试。

要在使用Alamofire时正确处理重试Publisher,请使用Alamofire的内置重试机制,如上所述

此外,DataResponsePublisher还提供了将支出DataResponse<Success, Failure>转换为Result<Success, Failure>值或SuccessFailure错误的值的功能。例如:

let publisher = AF.request(...).publishDecodable(type: DecodableType.self)
let resultPublisher = publisher.result()//提供一个AnyPublisher<Result<DecodableType, AFError>, Never>。
let valuePublisher = publisher.value()//提供一个AnyPublisher <DecodableType, AFError>。

与任何一样PublisherDataResponsePublisher可以与各种Combine API一起使用,允许Alamofire首次支持简单的同时请求。

//所有可取消的Combine API用法都必须存储其令牌以维护订阅。
var tokens: Set<AnyCancellable> = []

...

let first = AF.request(...).publishDecodable(type: First.self)
let second = AF.request(...).publishDecodable(type: Second.self)
let both = Publishers.CombineLatest(first, second)
both.sink { first, second in // DataResponse<First, AFError>, DataResponse<Second, AFError>
    debugPrint(first)
    debugPrint(second)
}
.store(in: &tokens)

顺序请求也是可能的:

//所有可取消的Combine API用法都必须存储其令牌以维护订阅。
var tokens: Set<AnyCancellable> = []

...

AF.request(...)
    .publishDecodable(type: First.self)
    .value()
    .flatMap {
        AF.request(...) // 使用 第一个 值 ,以 创建 第二个请求。
            .publishDecodable(type: Second.self)
    }
    .sink { second in // DataResponse<Second, AFError>
        debugPrint(second)
    }
    .store(in: &tokens)

订阅后,此转换链将发出第一个请求,然后创建第二个发布者,并在第二个请求完成时完成。

与所有Combine用法一样,必须注意确保通过维护AnyCancellable像这样的函数返回的令牌的生命周期来尽早取消订阅sink。如果过早取消请求,则响应的错误将设置为AFError.explicitlyCancelled

DownloadResponsePublisher

Alamofire还提供PublisherDownloadRequestS, DownloadResponsePublisher。其行为和功能与相同DataResponsePublisher

像大多数DownloadRequest的响应处理程序一样,从磁盘DownloadResponsePublisher读取Data以执行序列化,如果读取大量,可能会影响系统性能Data。建议您publishUnserialized()只接收URL?文件下载到的文件,然后对大文件执行自己的磁盘读取操作。

DataStreamPublisher

DataStreamPublisherPublisherDataStreamRequest秒。DataStreamRequest和Alamofire的其他Publishers 一样,它本身DataStreamPublisher可以返回Data从网络接收到的序列化的多个值以及最终完成事件。有关DataStreamRequest工作原理的更多信息,请参见我们的详细使用文档

网络可达性

NetworkReachabilityManager对主机和地址蜂窝和WiFi网络接口的可达性变化监听。

let manager = NetworkReachabilityManager(host: "www.apple.com")

manager?.startListening { status in
    print("Network Status Changed: \(status)")
}

请务必记住保留manager上述示例中的,否则不会报告任何状态更改。另外,请勿在host字符串中包含方案,否则可达性将无法正常运行。

使用网络可达性确定下一步操作时,需要记住一些重要的事情。

  • 不要使用可达性来确定是否应该发送网络请求。
    • 您应该总是发送它。
  • 恢复可达性后,请使用该事件重试失败的网络请求。
    • 即使网络请求仍然可能失败,这也是重试它们的好时机。
  • 网络可达性状态对于确定网络请求为何失败的原因可能很有用。
    • 如果网络请求失败,则告诉用户该网络请求由于脱机而失败比处理诸如“请求超时”之类的技术性错误更为有用。

或者,使用RequestRetrier诸如内置的RetryPolicy,而不是可达性更新来重试因网络故障而失败的请求,这可能会更简单,更可靠。默认情况下,RetryPolicy将在各种错误条件下重试幂等请求,包括脱机网络连接。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值