Alamofire 5 的使用 - 高級用法

即將離開簡書,請到掘金繼續關注我。謝謝!

本文掘金鏈接

為什么離開

此文章是對 Alamofire Advanced Usage 的翻譯,有需要的可以去看原文。

另外此文章的內容也保存到了我的 GitHub 倉庫,建議去 GitHub 閱讀,以獲得更好的閱讀體驗。如果覺得對你有用的,可以順手給個 Star。謝謝!

Alamofire 5 的使用 - 高級用法

這篇文章介紹的是 Alamofire 框架的高級用法,如果之前沒有看過基本用法的,可以先去看看 Alamofire 5 的使用 - 基本用法

Alamofire 是建立在 URLSession 和 URL 加載系統之上的。為了充分利用這個框架,建議您熟悉底層網絡的概念和功能。

建議閱讀

Session

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 實例

大多數應用程序將需要以各種方式自定義其 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-LanguageUser-Agent headers,但是可以使用任何 URLSessionConfiguration

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

let session = Session(configuration: configuration)

URLSessionConfiguration 不是設置 AuthorizationContent-Type headers 的建議位置。相反,可以使用提供的 headers APIs、ParameterEncoderRequestAdapter 將它們添加到 Request 中。

正如蘋果在其文檔中所述,在實例被添加到 URLSession(或者,在 Alamofire 的情況下,用于初始化 Session)之后對 URLSessionConfiguration 屬性進行修改沒有效果。

SessionDelegate

SessionDelegate 實例封裝了對各種 URLSessionDelegate 和相關協議回調的所有處理。SessionDelegate 還充當 Alamofire 生成的每個 RequestSessionStateDelegate,允許 Request 從創建它們的 Session 實例間接導入狀態。SessionDelegate 可以使用特定的 FileManager 實例進行自定義,該實例將用于任何磁盤訪問,例如訪問要通過 UploadRequest 上傳的文件或通過 DownloadRequest 下載的文件。

let delelgate = SessionDelegate(fileManager: .default)

startRequestsImmediately

默認情況下,Session 將在添加至少一個響應 handler 后立即對 Request 調用 resume()。將 startRequestsImmediately 設置為 false 需要手動調用所有請求的 resume() 方法。

let session = Session(startRequestsImmediately: false)

SessionDispatchQueue

默認情況下,Session 實例對所有異步工作使用單個 DispatchQueue。這包括 URLSessiondelegate OperationQueueunderlyingQueue,用于所有 URLRequest 創建、所有響應序列化工作以及所有內部 SessionRequest 狀態的改變。如果性能分析顯示瓶頸在于 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必須是串行隊列,但 requestQueueserializationQueue 可以是串行或并行隊列。通常建議使用串行隊列,除非性能分析顯示工作被延遲,在這種情況下,使隊列并行可能有助于提高整體性能。

添加 RequestInterceptor

Alamofire 的 RequestInterceptor 協議(RequestAdapter & RequestRetrier)提供了重要而強大的請求自適應和重試功能。它可以在 SessionRequest 層級使用。有關 RequestInterceptor 和 Alamofire 包含的各種實現(如 RetryPolicy)的更多詳細信息,請參見下文

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

添加 ServerTrustManager

Alamofire 的 ServerTrustManager 類封裝了域名和遵循 ServerTrustEvaluating協議的類型實例之間的映射,這提供了定制 Session 處理 TLS 安全性的能力。這包括使用證書和公鑰固定以及證書吊銷檢查。有關更多信息,請參閱有關 ServerTrustManagerServerTrustEvaluating 的部分。初始化 ServerTrustManger 非常簡單,只需提供域名與要執行的計算類型之間的映射即可:

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

有關評估服務器信任的詳細信息,請參閱下面的詳細文檔。

添加 RedirectHandler

Alamofire 的 RedirectHandler 協議定制了 HTTP 重定向響應的處理。它可以在 SessionRequest 層級使用。Alamofire 包含了遵循 RedirectHandler 協議的 Redirector 類型,并提供對重定向的簡單控制。有關重定向處理程序的詳細信息,請參閱下面的詳細文檔。

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

添加 CachedResponseHandler

Alamofire 的 CachedResponseHandler 協議定制了響應的緩存,可以在 SessionRequest 層級使用。Alamofire 包含 ResponseCacher 類型,它遵循 CachedResponseHandler 協議并提供對響應緩存的簡單控制。有關詳細信息,請參閱下面的詳細文檔。

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

添加 EventMonitor

Alamofire 的 EventMonitor 協議提供了對 Alamofire 內部事件的強大洞察力。它可以用來提供日志和其他基于事件的特性。Session 在初始化時接受遵循 EventMonitor 協議的實例的數組。

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

URLSession 創建實例

除了前面提到的便利初始化器之外,還可以直接從 URLSession 初始化 Session。但是,在使用這個初始化器時需要記住幾個要求,因此建議使用便利初始化器。其中包括:

  • Alamofire 不支持為在后臺使用而配置的 URLSession。初始化 Session 時,這將導致運行時錯誤。
  • 必須創建 SessionDelegate 實例并將其作為 URLSessiondelegate,以及傳遞給 Session 的初始化器。
  • 必須將自定義 OperationQueue 作為 URLSessiondelegateQueue。此隊列必須是串行隊列,它必須具有備用 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 執行的每個請求都由特定的類、DataRequestUploadRequestDownloadRequest 封裝。這些類中的每一個都封裝了每種類型請求所特有的功能,但是 DataRequestDownloadRequest 繼承自一個公共的父類 RequestUploadRequest 繼承自 DataRequest)。Request 實例從不直接創建,而是通過各種 request 方法之一從會話 Session 中自動生成。

請求管道

一旦使用 Request 子類的初始參數或 URLRequestConvertible 創建了它,它就會通過組成 Alamofire 請求管道的一系列步驟進行傳遞。對于成功的請求,這些請求包括:

  1. 初始參數(如 HTTP 方法、headers 和參數)被封裝到內部 URLRequestConvertible 值中。如果直接傳遞 URLRequestConvertible 值,則使用該值時將保持不變。
  2. URLRequestConvertible 值調用 asURLRequest(),創建第一個 URLRequest 值。此值將傳遞給 Request 并存儲在 requests 中。
  3. 如果有任何 SessionRequest RequestAdapterRequestInterceptor,則使用先前創建的 URLRequest 調用它們。然后將調整后的 URLRequest 傳遞給 Request 并存儲在 requests 中。
  4. Session 調用 Request 創建的 URLSessionTask,以基于 URLRequest 執行網絡請求。
  5. 完成 URLSessionTask 并收集 URLSessionTaskMetrics 后,Request 將執行其 Validator
  6. 請求執行已附加的任何響應 handlers,如 responseDecodable

在這些步驟中的任何一個,都可以通過創建或接收的 Error 值來表示失敗,然后將錯誤值傳遞給關聯的 Request。例如,除了步驟 1 和 4 之外,上面的所有其他步驟都可以創建一個Error,然后傳遞給響應 handlers 或可供重試。下面是一些可以或不能在整個請求管道中失敗的示例。

  • 參數封裝不能失敗。
  • 調用 asURLRequest() 時,任何 URLRequestConvertible 值都可能創建錯誤。這允許初始驗證各種 URLRequest 屬性或參數編碼失敗。
  • RequestAdapter 在自適應過程中可能會失敗,可能是由于缺少授權 token。
  • URLSessionTask 創建不能失敗。
  • URLSessionTask 可能由于各種原因帶有錯誤地完成,包括網絡可用性和取消。這些 Error 值將傳遞回給 Request
  • 響應 handlers 可以產生任何錯誤,通常是由于無效響應或其他分析錯誤。

一旦將錯誤傳遞給 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
}

請求在創建后以 .initialized 狀態啟動。通過調用適當的生命周期方法,可以掛起、恢復和取消 Request

  • resume() 恢復或啟動請求的網絡流量。如果 startRequestsImmediatelytrue,則在將響應 handlers 添加到 Request 后自動調用此函數。
  • suspend() 掛起或暫停請求及其網絡流量。此狀態下的 Request 可以繼續,但只有 DownloadRequest 才能繼續傳輸數據。其他 Request 將重新開始。
  • cancel() 取消請求。一旦進入此狀態,就無法恢復或掛起 Request。調用 cancel() 時,將使用 AFError.explicitlyCancelled 實例設置請求的 error 屬性。如果一個 Request 被恢復并且在以后沒有被取消,那么它將在所有響應驗證器和響應序列化器運行之后到達 .finished 狀態。但是,如果在請求達到 .finished 狀態后將其他響應序列化器添加到該請求,則它將轉換回 .resumed 狀態并再次執行網絡請求。

進度

為了跟蹤請求的進度,Request 提供了 uploadProgressdownloadProgress 屬性以及基于閉包的 uploadProgressdownloadProgress 方法。與所有基于閉包的 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 提供給 UploadRequestData 對象的長度。
    • 通過作為 UploadRequest 的上傳 body 提供的磁盤上文件的長度。
    • 通過根據請求的 Content-Length header 的值(如果已手動設置)。
  • 對于下載進度,只有一個要求:
    • 服務器響應必須包含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 協議提供了對響應緩存的控制和定制。除了每個 SessionCachedResponseHandlers 之外,每個 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。嘗試設置多個將導致運行時異常。

Credentials

為了利用 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)
    }

RequestURLRequest

Request 發出的每個網絡請求最終封裝在由傳遞給 Session 請求方法之一的各種參數創建的 URLRequest 值中。Request 將在其 requests 數組屬性中保留這些 URLRequest 的副本。這些值既包括從傳遞的參數創建的初始 URLRequest,也包括由 RequestInterceptors 創建的任何 URLRequest。但是,該數組不包括代表 Request 發出的 URLSessionTask 執行的 URLRequest。要檢查這些值,tasks 屬性允許訪問 Request 執行的所有 URLSessionTask

URLSessionTask

在許多方面,各種 Request 子類充當 URLSessionTask 的包裝器,提供與特定類型任務交互的特定 API。這些任務通過 tasks 數組屬性在 Request 實例上可見。這包括為 Request 創建的初始任務,以及作為重試過程的一部分創建的任何后續任務,每次重試一個任務。

響應

請求完成后,每個 Request 可能都有一個可用的 HTTPURLResponse 值。此值僅在請求未被取消且沒有發出網絡請求失敗時可用。此外,如果重試請求,則只有最后一個響應可用。可以從 tasks 屬性中的 URLSessionTasks 獲得中間的響應。

URLSessionTaskMetrics

Alamofire 為 Request 執行的每個 URLSessionTask 收集 URLSessionTaskMetrics 值。這些值存儲在 metrics 屬性,每個值對應于同一索引中的 tasks 中的 URLSessionTask

URLSessionTaskMetrics 也可從 Alamofire 的各種響應類型中訪問,如 DataResponse。例如:

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

DataRequest

DataRequestRequest 的一個子類,它封裝了 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

UploadRequestDataRequest 的一個子類,它封裝 URLSessionUploadTask、將 Data、磁盤上的文件或 InputStream 上傳到遠程服務器。

其他狀態

除了 DataRequest 提供的屬性外,UploadRequest 還有一些屬性。其中包括一個 FileManager 實例,用于在上傳文件時自定義對磁盤的訪問,以及 uploadupload 封裝了用于描述請求的 URLRequestConvertible 值和確定要執行的上傳類型的 Uploadable 值。

DownloadRequest

DownloadRequestRequest 的一個具體子類,它封裝了 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 支持的驗證版本與 DataRequestUploadRequest 略有不同,因為它的數據被下載到磁盤上。

public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse, _ fileURL: URL?)

必須使用提供的 fileURL 訪問下載的 Data,而不是直接訪問下載的 Data。否則,DownloadRequest 的驗證器的功能與 DataRequest 的相同。

使用 RequestInterceptor 調整和重試請求

Alamofire 的 RequestInterceptor 協議(由 RequestAdapterRequestRetrier 協議組成)支持強大的每個 Session 和每個 Request 功能。其中包括身份驗證系統,在該系統中,向每個 Request 添加一個常用的 headers,并在授權過期時重試 Request。此外,Alamofire 還包含一個內置的 RetryPolicy 類型,當由于各種常見的網絡錯誤而導致請求失敗時,可以輕松重試。

RequestAdapter

Alamofire 的 RequestAdapter 協議允許在通過網絡發出之前檢查和修改 Session 執行的每個 URLRequest。適配器的一個非常常見的用途,是在特定類型身份驗證后面將 Authorization header 添加請求。

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

它的參數包括:

  • urlRequest:最初從用于創建請求的參數或 URLRequestConvertible 值創建的 urlRequest
  • session:創建調用適配器的 RequestSession
  • 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))
}

RequestRetrier

Alamofire 的 RequestRetrier 協議允許重試在執行時遇到錯誤的請求。這包括在 Alamofire 的請求管道的任何階段產生的錯誤。

RequestRetrier 協議只有一個方法:

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

它的參數包括:

  • request: 遇到錯誤的 Request
  • session: 管理 RequestSession
  • 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 協議提供的證書或公鑰固定。

使用 ServerTrustManagerServerTrustEvaluating 評估服務器信任

ServerTrustEvaluting

ServerTrustEvaluting 協議提供了執行任何類型服務器信任評估的方法。它只有一個方法:

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

此方法提供從底層 URLSession 接收的 SecTrust 值和主機 String,并提供執行各種評估的機會。

Alamofire 包括許多不同類型的信任評估器,為評估過程提供可組合的控制:

  • DefaultTrustEvaluator:使用默認服務器信任評估,同時允許您控制是否驗證質詢提供的主機。
  • RevocationTrustEvaluator:檢查接收到的證書的狀態以確保它沒有被吊銷。這通常不會在每個請求上執行,因為它需要網絡請求開銷。
  • PinnedCertificatesTrustEvaluator: 使用提供的證書驗證服務器信任。如果某個固定證書與某個服務器證書匹配,則認為服務器信任有效。此評估器還可以接受自簽名證書。
  • PublicKeysTrustEvaluator: 使用提供的公鑰驗證服務器信任。如果某個固定公鑰與某個服務器證書公鑰匹配,則認為服務器信任有效。
  • CompositeTrustEvaluator: 評估一個 ServerTrustEvaluating 值數組,只有在所有數組中值都成功時才成功。此類型可用于組合,例如,RevocationTrustEvaluatorPinnedCertificatesTrustEvaluator
  • DisabledEvaluator:此評估器應僅在調試方案中使用,因為它禁用所有求值,而這些求值又將始終認為任何服務器信任都是有效的。此評估器不應在生產環境中使用!

ServerTrustManager

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 握手成功:
    • 證書鏈必須有效。
    • 證書鏈必須包含一個固定的公鑰。
    • 質詢主機必須與證書鏈中的證書中的主機匹配。
  • 對其他主機的請求將產生一個錯誤,因為服務器信任管理器要求默認評估所有主機。
子類化 ServerTrustPolicyManager

如果發現自己需要更靈活的服務器信任策略匹配行為(例如通配符域名),那么子類化 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
    }
}

應用傳輸安全 (App Transport Security)

在 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 允許使用 URLSessionDataDelegateURLSessionTaskDelegate 方法自定義緩存和重定向行為。Alamofire 將這些定制點呈現為 CachedResponseHandlerRedirectHandler 協議。

CachedResponseHandler

CachedResponseHandler 協議允許控制將 HTTP 響應緩存到與發出請求的 Session 相關聯的 URLCache 實例中。該協議只有一個方法:

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

從方法簽名中可以看出,此控制僅適用于使用底層 URLSessionDataTask 進行網絡傳輸的 Request,這些請求包括 DataRequestUploadRequest(因為 URLSessionUploadTaskURLSessionDataTask 的一個子類)。考慮響應進行緩存的條件非常廣泛,因此最好查看 URLSessionDataDelegate 方法 urlSession(_:dataTask:willCacheResponse:completionHandler:) 的文檔。一旦考慮將響應用于緩存,就可以進行各種有價值的操作:

  • 通過返回 nil CachedURLResponse 來防止完全緩存響應。
  • 修改 CachedURLResponsestoragePolicy,以更改緩存值的存放位置。
  • 直接修改底層 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 可以在 SessionRequest 的基礎上使用,如上所述。

RedirectHandler

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

EventMonitor 協議允許觀察和檢查大量內部 Alamofire 事件。這些事件包括由 Alamofire 實現的所有 URLSessionDelegateURLSessionTaskDelegateURLSessionDownloadDelegate 方法以及大量內部 Request 事件。除了這些事件(默認情況下是不起作用的空方法)之外,EventMonitor 協議還需要一個 DispatchQueue,在這個 DispatchQueue 上調度所有事件以保持性能。此 DispatchQueue 默認為 .main,但對于任何自定義一致類型,建議使用專用串行隊列。

Logging

也許 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 有兩個主要目標:

  1. 使原型和工具的網絡請求易于實現
  2. 作為 APP 網絡請求的通用基礎

它通過使用強大的抽象、提供有用的默認值和包含常見任務的實現來實現這些目標。然而,一旦 Alamofire 的使用超出了一些請求,就有必要超越高級的、默認的實現,進入為特定應用程序定制的行為。Alamofire 提供 URLConvertibleURLRequestConvertible 協議來幫助進行這種定制。

URLConvertible

可以使用遵循 URLConvertible 協議的類型來構造 URL,然后使用 URL 在內部構造 URL 請求。默認情況下,StringURLURLComponents 遵循了 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 協議的類型可用于構造 URLRequest。默認情況下,URLRequest 遵循 URLRequestConvertible,允許將其直接傳遞到 requestuploaddownload 方法中。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 URLConvertibleURLRequestConvertible 協議以及 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 協議提供響應處理。

處理沒有序列化的響應

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

與所有響應 handlers 一樣,所有序列化工作(在本例中為“無”)都在內部隊列上執行,并在傳遞給方法的 queue 上調用 completion handler。這意味著在默認情況下不需要將其分派回主隊列。但是,如果要在 completion handler 中執行任何重要的工作,建議將自定義隊列傳遞給響應方法,必要時在 handler 本身中將分派回主隊列。

ResponseSerializer

ResponseSerializer 協議由 DataResponseSerializerProtocolDownloadResponseSerializerProtocol 協議組成。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:) 方法實現對大型下載的自定義處理可能更為合適。

ResponseSerializerdataPreprocessoremptyResponseMethodsemptyResponseCodes 提供了各種默認實現,這些實現可以在自定義類型中進行定制,如 Alamofire 附帶的各種 ResponseSerializer

所有 ResponseSerializer 的使用都通過 DataRequestDownloadRequest 上的方法進行:

// 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 和泛型響應解析推薦用此方法。

DataResponseSerializer

DataRequestDownloadRequest 上調用 responseData(queue:completionHandler:) 使用 DataResponseSerializer 驗證 Data 是否已正確返回(除非 emptyResponseMethodsemptyResponseCodes 允許,否則不允許空響應),并將該 Data 傳遞到 dataPreprocessor。此響應 handler 對于自定義 Data 處理非常有用,但通常不是必需的。

StringResponseSerializer

DataRequestDownloadRequest 調用 responseString(queue:encoding:completionHa 使用 StringResponseSerializer 驗證 Data 是否已正確返回(除非 emptyResponseMethodsemptyResponseCodes 允許,否則不允許空響應)并將該 Data 傳遞到 dataPreprocessor。然后,使用從 HTTPURLResponse 解析的 String.Encoding 處理 Data,并初始化一個 String

JSONResponseSerializer

DataRequestDownloadRequest 上調用 responseJSON(queue:options:completionHandler) 使用 JSONResponseSerializer 驗證 Data 是否已正確返回(除非 emptyResponseMethodsemptyResponseCodes 允許,否則不允許空響應),并將該 Data 傳遞給 dataPreprocessor。然后,使用提供的選項把預處理的 Data 傳遞給 JSONSerialization.jsonObject(with:options:)。不再推薦使用此序列化程序。相反,使用 DecodableResponseSerializer 提供了更好的快速體驗。

DecodableResponseSerializer

DataRequestDownloadRequest 上調用 responseDecodable(of:queue:decoder:completionHandler) 使用 DecodableResponseSerializer 來驗證 Data 是否已正確返回(除非 emptyResponseMethodsemptyResponseCodes 允許,否則不允許空響應),并將該 Data 傳遞給 dataPreprocessor。然后,預處理的 Data 傳遞給提供的 DataDecoder,并解析為提供的或推斷的 Decodable 類型。

自定義響應 Handlers

除了包含在 Alamofire 中的靈活的 ResponseSerializer 之外,還有其他定制響應處理的方法。

響應轉換

使用現有的 ResponseSerializer 然后轉換輸出是定制響應 handler 的最簡單方法之一。DataResponseDownloadResponse 都有 maptryMapmapErrortryMapError 方法,這些方法可以轉換響應,同時保留與響應相關聯的元數據。例如,可以使用 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 將在各種錯誤條件下重試等冪請求,包括離線的網絡連接。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容