Alamofire 5 的使用 - 高級用法

即將離開簡書温眉,請到掘金繼續(xù)關注我凌蔬。謝謝!

本文掘金鏈接

為什么離開

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

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

Alamofire 5 的使用 - 高級用法

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

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

建議閱讀

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

創(chuàng)建自定義的 Session 實例

大多數(shù)應用程序將需要以各種方式自定義其 Session 實例的行為穷蛹。實現(xiàn)這一點的最簡單方法是使用以下便利初始化器鬼雀,并將結果存儲在整個應用程序中使用的單個實例中。

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 創(chuàng)建 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 和相關協(xié)議回調的所有處理。SessionDelegate 還充當 Alamofire 生成的每個 RequestSessionStateDelegate彬碱,允許 Request 從創(chuàng)建它們的 Session 實例間接導入狀態(tài)灵奖。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 創(chuàng)建、所有響應序列化工作以及所有內部 SessionRequest 狀態(tài)的改變腰素。如果性能分析顯示瓶頸在于 URLRequest 的創(chuàng)建或響應序列化献起,則可以為 Session 的每個工作區(qū)域提供單獨的 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 協(xié)議(RequestAdapter & RequestRetrier)提供了重要而強大的請求自適應和重試功能。它可以在 SessionRequest 層級使用。有關 RequestInterceptor 和 Alamofire 包含的各種實現(xiàn)(如 RetryPolicy)的更多詳細信息,請參見下文

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

添加 ServerTrustManager

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

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

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

添加 RedirectHandler

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

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

添加 CachedResponseHandler

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

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

添加 EventMonitor

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

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

URLSession 創(chuàng)建實例

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

  • Alamofire 不支持為在后臺使用而配置的 URLSession和簸。初始化 Session 時彭雾,這將導致運行時錯誤。
  • 必須創(chuàng)建 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 執(zhí)行的每個請求都由特定的類浩村、DataRequest做葵、UploadRequestDownloadRequest 封裝。這些類中的每一個都封裝了每種類型請求所特有的功能穴亏,但是 DataRequestDownloadRequest 繼承自一個公共的父類 RequestUploadRequest 繼承自 DataRequest)蜂挪。Request 實例從不直接創(chuàng)建,而是通過各種 request 方法之一從會話 Session 中自動生成嗓化。

請求管道

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

  1. 初始參數(shù)(如 HTTP 方法严肪、headers 和參數(shù))被封裝到內部 URLRequestConvertible 值中。如果直接傳遞 URLRequestConvertible 值谦屑,則使用該值時將保持不變驳糯。
  2. URLRequestConvertible 值調用 asURLRequest(),創(chuàng)建第一個 URLRequest 值氢橙。此值將傳遞給 Request 并存儲在 requests 中酝枢。
  3. 如果有任何 SessionRequest RequestAdapterRequestInterceptor,則使用先前創(chuàng)建的 URLRequest 調用它們悍手。然后將調整后的 URLRequest 傳遞給 Request 并存儲在 requests 中帘睦。
  4. Session 調用 Request 創(chuàng)建的 URLSessionTask袍患,以基于 URLRequest 執(zhí)行網絡請求。
  5. 完成 URLSessionTask 并收集 URLSessionTaskMetrics 后竣付,Request 將執(zhí)行其 Validator诡延。
  6. 請求執(zhí)行已附加的任何響應 handlers,如 responseDecodable古胆。

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

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

一旦將錯誤傳遞給 Request颓哮,Request 將嘗試運行與 SessionRequest 關聯(lián)的任何 RequestRetrier家妆。如果任何 RequestRetrier 選擇重試該 Request,則將再次運行完整的管道冕茅。RequestRetrier也會產生 Error伤极,但這些錯誤不會觸發(fā)重試。

Request

盡管 Request 不封裝任何特定類型的請求姨伤,但它包含 Alamofire 執(zhí)行的所有請求所共有的狀態(tài)和功能哨坪。這包括:

狀態(tài)

所有 Request 類型都包含狀態(tài)的概念,表示 Request 生命周期中的主要事件乍楚。

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

請求在創(chuàng)建后以 .initialized 狀態(tài)啟動当编。通過調用適當?shù)纳芷诜椒ǎ梢話炱鹜较⒒謴秃腿∠?Request凌箕。

  • resume() 恢復或啟動請求的網絡流量拧篮。如果 startRequestsImmediatelytrue,則在將響應 handlers 添加到 Request 后自動調用此函數(shù)牵舱。
  • suspend() 掛起或暫停請求及其網絡流量。此狀態(tài)下的 Request 可以繼續(xù)缺虐,但只有 DownloadRequest 才能繼續(xù)傳輸數(shù)據芜壁。其他 Request 將重新開始。
  • cancel() 取消請求高氮。一旦進入此狀態(tài)慧妄,就無法恢復或掛起 Request。調用 cancel() 時剪芍,將使用 AFError.explicitlyCancelled 實例設置請求的 error 屬性塞淹。如果一個 Request 被恢復并且在以后沒有被取消,那么它將在所有響應驗證器和響應序列化器運行之后到達 .finished 狀態(tài)罪裹。但是饱普,如果在請求達到 .finished 狀態(tài)后將其他響應序列化器添加到該請求,則它將轉換回 .resumed 狀態(tài)并再次執(zhí)行網絡請求状共。

進度

為了跟蹤請求的進度套耕,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 協(xié)議提供了對 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 協(xié)議提供了對響應緩存的控制和定制芝薇。除了每個 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 發(fā)出的每個網絡請求最終封裝在由傳遞給 Session 請求方法之一的各種參數(shù)創(chuàng)建的 URLRequest 值中。Request 將在其 requests 數(shù)組屬性中保留這些 URLRequest 的副本苦丁。這些值既包括從傳遞的參數(shù)創(chuàng)建的初始 URLRequest浸颓,也包括由 RequestInterceptors 創(chuàng)建的任何 URLRequest。但是旺拉,該數(shù)組不包括代表 Request 發(fā)出的 URLSessionTask 執(zhí)行的 URLRequest产上。要檢查這些值,tasks 屬性允許訪問 Request 執(zhí)行的所有 URLSessionTask蛾狗。

URLSessionTask

在許多方面晋涣,各種 Request 子類充當 URLSessionTask 的包裝器,提供與特定類型任務交互的特定 API沉桌。這些任務通過 tasks 數(shù)組屬性在 Request 實例上可見谢鹊。這包括為 Request 創(chuàng)建的初始任務,以及作為重試過程的一部分創(chuàng)建的任何后續(xù)任務留凭,每次重試一個任務佃扼。

響應

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

URLSessionTaskMetrics

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

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

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

DataRequest

DataRequestRequest 的一個子類,它封裝了 URLSessionDataTask巩踏,將服務器響應下載到存儲在內存中的 Data 中。因此续搀,必須認識到塞琼,超大下載量可能會對系統(tǒng)性能產生不利影響。對于這些類型的下載禁舷,建議使用 DownloadRequest 將數(shù)據保存到磁盤彪杉。

其他狀態(tài)

除了 Request 提供的屬性之外,DataRequest 還有一些屬性牵咙。其中包括 data(這是服務器響應的累積 Data)和 convertible(這是創(chuàng)建 DataRequest 時使用的 URLRequestConvertible)派近,其中包含創(chuàng)建實例的原始參數(shù)。

驗證

默認情況下洁桌,DataRequest 不驗證響應渴丸。相反,必須向其中添加對 validate() 的調用另凌,以驗證各種屬性是否有效鳖昌。

public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> Result<Void, Error>

默認情況下囊咏,添加 validate() 確保響應狀態(tài)代碼在 200..<300 范圍內,并且響應的 Content-Type 與請求的 Accept 匹配。通過傳遞 Validation 閉包可以進一步定制驗證:

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

UploadRequest

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

其他狀態(tài)

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

DownloadRequest

DownloadRequestRequest 的一個具體子類,它封裝了 URLSessionDownloadTask司浪,將響應數(shù)據下載到磁盤泊业。

其他狀態(tài)

DownloadRequest 除了由 Request 提供的屬性外把沼,還有一些屬性。其中包括取消 DownloadRequest 時生成的數(shù)據 resumeData(可用于以后繼續(xù)下載)和 fileURL(下載完成后下載文件對應的 URL)吁伺。

取消

除了支持 Request 提供的 cancel() 方法外饮睬,DownloadRequest 還包括 cancel(producingResumeData shouldProduceResumeData: Bool),如果可能的話篮奄,可以選擇在取消時設置 resumeData 屬性捆愁,以及 cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void),它將生成的恢復數(shù)據提供給傳遞進來的閉包窟却。

AF.download(...)
    .cancel { resumeData in
        ...
    }

驗證

DownloadRequest 支持的驗證版本與 DataRequestUploadRequest 略有不同昼丑,因為它的數(shù)據被下載到磁盤上。

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

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

使用 RequestInterceptor 調整和重試請求

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

RequestAdapter

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

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

它的參數(shù)包括:

  • urlRequest:最初從用于創(chuàng)建請求的參數(shù)或 URLRequestConvertible 值創(chuàng)建的 urlRequest依鸥。
  • session:創(chuàng)建調用適配器的 RequestSession
  • completion: 一個必須調用的悼沈、用來表示適配器已完成的異步 completion handler贱迟。它的異步特性使 RequestAdapter 能夠在請求通過網絡發(fā)送之前從網絡或磁盤訪問異步資源。提供給 completion 閉包的 Result 可以返回帶有修改后的 URLRequest.success 值絮供,或者返回帶有關聯(lián)錯誤的 .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 協(xié)議允許重試在執(zhí)行時遇到錯誤的請求。這包括在 Alamofire 的請求管道的任何階段產生的錯誤忧换。

RequestRetrier 協(xié)議只有一個方法:

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

它的參數(shù)包括:

  • request: 遇到錯誤的 Request恬惯。
  • session: 管理 RequestSession
  • error: 觸發(fā)重試的 Error亚茬,通常是一個 AFError酪耳。
  • completion: 必須調用的異步 completion handler,以表示 Request 是否需要重試刹缝。調用的時候必須傳入 RetryResult碗暗。

RetryResult 類型表示在 RequestRetrier 中實現(xiàn)的任何邏輯的結果。定義為:

/// 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 連接是保護敏感數(shù)據的重要步驟颂砸。默認情況下洲守,Alamofire 接收與 URLSession 相同的自動 TLS 證書和證書鏈驗證。雖然這保證了證書鏈的有效性沾凄,但并不能防止中間人(MITM)攻擊或其他潛在的漏洞。為了減輕 MITM 攻擊知允,處理敏感客戶數(shù)據或財務信息的應用程序應使用 Alamofire 的 ServerTrustEvaluating 協(xié)議提供的證書或公鑰固定撒蟀。

使用 ServerTrustManagerServerTrustEvaluating 評估服務器信任

ServerTrustEvaluting

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

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

此方法提供從底層 URLSession 接收的 SecTrust 值和主機 String温鸽,并提供執(zhí)行各種評估的機會保屯。

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

  • DefaultTrustEvaluator:使用默認服務器信任評估涤垫,同時允許您控制是否驗證質詢提供的主機姑尺。
  • RevocationTrustEvaluator:檢查接收到的證書的狀態(tài)以確保它沒有被吊銷。這通常不會在每個請求上執(zhí)行蝠猬,因為它需要網絡請求開銷切蟋。
  • PinnedCertificatesTrustEvaluator: 使用提供的證書驗證服務器信任。如果某個固定證書與某個服務器證書匹配榆芦,則認為服務器信任有效柄粹。此評估器還可以接受自簽名證書。
  • PublicKeysTrustEvaluator: 使用提供的公鑰驗證服務器信任匆绣。如果某個固定公鑰與某個服務器證書公鑰匹配驻右,則認為服務器信任有效。
  • CompositeTrustEvaluator: 評估一個 ServerTrustEvaluating 值數(shù)組崎淳,只有在所有數(shù)組中值都成功時才成功堪夭。此類型可用于組合,例如,RevocationTrustEvaluatorPinnedCertificatesTrustEvaluator森爽。
  • DisabledEvaluator:此評估器應僅在調試方案中使用恨豁,因為它禁用所有求值,而這些求值又將始終認為任何服務器信任都是有效的拗秘。此評估器不應在生產環(huán)境中使用圣絮!

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

如果發(fā)現(xiàn)自己需要更靈活的服務器信任策略匹配行為(例如通配符域名)悉罕,那么子類化 ServerTrustManager赤屋,并用自己的自定義實現(xiàn)重寫 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 可能不會有任何效果类早。如果您持續(xù)看到 CFNetwork SSLHandshake failed (-9806) 錯誤,則可能遇到了此問題嗜逻。蘋果的 ATS 系統(tǒng)會覆蓋整個質詢系統(tǒng)涩僻,除非您在應用程序的 plist 中配置 ATS 設置以禁用足夠多的 ATS 設置,以允許您的應用程序評估服務器信任栈顷。如果遇到此問題(自簽名證書的概率很高)令哟,可以通過將 NSAppTransportSecurity 設置添加到 Info.plist 來解決此問題。您可以使用 nscurl 工具的 --ats-diagnostics 選項對主機執(zhí)行一系列測試妨蛹,以查看可能需要哪些 ATS 重寫屏富。

在本地網絡中使用自簽名證書

如果嘗試連接到本地主機上運行的服務器,并且使用自簽名證書蛙卤,則需要將以下內容添加到 Info.plist 中狠半。

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

根據蘋果文檔噩死,將 NSAllowsLocalNetworking 設置為 YES 允許加載本地資源,而不必為應用程序的其余部分禁用 ATS神年。

自定義緩存和重定向處理

URLSession 允許使用 URLSessionDataDelegateURLSessionTaskDelegate 方法自定義緩存和重定向行為已维。Alamofire 將這些定制點呈現(xiàn)為 CachedResponseHandlerRedirectHandler 協(xié)議。

CachedResponseHandler

CachedResponseHandler 協(xié)議允許控制將 HTTP 響應緩存到與發(fā)出請求的 Session 相關聯(lián)的 URLCache 實例中已日。該協(xié)議只有一個方法:

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

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

  • 通過返回 nil CachedURLResponse 來防止完全緩存響應。
  • 修改 CachedURLResponsestoragePolicy霉旗,以更改緩存值的存放位置痴奏。
  • 直接修改底層 URLResponse,添加或刪除值厌秒。
  • 修改與響應關聯(lián)的 Data(如果有)读拆。

Alamofire 包含遵循 CachedResponseHandler 協(xié)議的 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 協(xié)議允許控制特定 Request 的重定向行為。它只有一個方法:

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

此方法提供了修改重定向的 URLRequest 或傳遞 nil 以完全禁用重定向的機會屠列。Alamofire 提供了遵循 RedirectHandler 協(xié)議的 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 協(xié)議允許觀察和檢查大量內部 Alamofire 事件。這些事件包括由 Alamofire 實現(xiàn)的所有 URLSessionDelegate苛让、URLSessionTaskDelegateURLSessionDownloadDelegate 方法以及大量內部 Request 事件沟蔑。除了這些事件(默認情況下是不起作用的空方法)之外,EventMonitor 協(xié)議還需要一個 DispatchQueue狱杰,在這個 DispatchQueue 上調度所有事件以保持性能瘦材。此 DispatchQueue 默認為 .main,但對于任何自定義一致類型仿畸,建議使用專用串行隊列食棕。

Logging

也許 EventMonitor 協(xié)議的最大用途是實現(xiàn)相關事件的日志記錄朗和。一個簡單的實現(xiàn)可能如下所示:

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

創(chuàng)建請求

作為一個框架,Alamofire 有兩個主要目標:

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

它通過使用強大的抽象簿晓、提供有用的默認值和包含常見任務的實現(xiàn)來實現(xiàn)這些目標眶拉。然而,一旦 Alamofire 的使用超出了一些請求憔儿,就有必要超越高級的忆植、默認的實現(xiàn),進入為特定應用程序定制的行為谒臼。Alamofire 提供 URLConvertibleURLRequestConvertible 協(xié)議來幫助進行這種定制朝刊。

URLConvertible

可以使用遵循 URLConvertible 協(xié)議的類型來構造 URL,然后使用 URL 在內部構造 URL 請求屋休。默認情況下坞古,StringURLURLComponents 遵循了 URLConvertible 協(xié)議劫樟,允許將它們中的任何一個作為 URL 參數(shù)傳遞給 request痪枫、uploaddownload 方法:

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 協(xié)議的類型可用于構造 URLRequest奶陈。默認情況下,URLRequest 遵循 URLRequestConvertible附较,允許將其直接傳遞到 request吃粒、uploaddownload 方法中。Alamofire 使用 URLRevestExchange 作為請求管道中流動的所有請求的基礎拒课。直接使用 URLRequest 是在 Alamofire 提供的 ParamterEncoder 之外自定義 URLRequest 創(chuàng)建的推薦方法徐勃。

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 的自定義類型,以確保所請求端點的一致性早像。這種方法可以用來消除服務器端的不一致性僻肖,提供類型安全的路由,以及管理其他狀態(tài)卢鹦。

路由請求

隨著應用程序規(guī)模的增長臀脏,在構建網絡堆棧時采用通用模式非常重要。該設計的一個重要部分是如何路由您的請求冀自。Alamofire URLConvertibleURLRequestConvertible 協(xié)議以及 Router 設計模式都可以幫助您揉稚。

“router” 是定義“路由”或請求組件的類型。這些組件可以包括 URLRequest 的部分熬粗、發(fā)出請求所需的參數(shù)以及每個請求的各種 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 可以包括請求的參數(shù)。使用 Alamofire 的 ParameterEncoder 協(xié)議和包含的編碼器驻呐,任何 Encodable 類型都可以用作參數(shù):

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 可以擴展到具有任意數(shù)量可配置屬性的任意數(shù)量的端點巷怜,但是一旦達到了一定的復雜程度葛超,就應該考慮將一個大的 router 分成較小的 router 作為 API 的一部分。

響應處理

Alamofire 通過各種 response 方法和 ResponseSerializer 協(xié)議提供響應處理延塑。

處理沒有序列化的響應

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 一樣,所有序列化工作(在本例中為“無”)都在內部隊列上執(zhí)行宋雏,并在傳遞給方法的 queue 上調用 completion handler芜飘。這意味著在默認情況下不需要將其分派回主隊列。但是磨总,如果要在 completion handler 中執(zhí)行任何重要的工作嗦明,建議將自定義隊列傳遞給響應方法,必要時在 handler 本身中將分派回主隊列蚪燕。

ResponseSerializer

ResponseSerializer 協(xié)議由 DataResponseSerializerProtocolDownloadResponseSerializerProtocol 協(xié)議組成娶牌。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 方法是通過從磁盤讀取下載的數(shù)據并調用 serialize 來實現(xiàn)的馆纳。因此诗良,使用上面提到的 DownloadRequest 的響應 response(queue:completionHandler:) 方法實現(xiàn)對大型下載的自定義處理可能更為合適。

ResponseSerializerdataPreprocessor鲁驶、emptyResponseMethodsemptyResponseCodes 提供了各種默認實現(xiàn)鉴裹,這些實現(xiàn)可以在自定義類型中進行定制,如 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总处。不建議使用此方法,僅為與現(xiàn)有的 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 之外亡蓉,還有其他定制響應處理的方法晕翠。

響應轉換

使用現(xiàn)有的 ResponseSerializer 然后轉換輸出是定制響應 handler 的最簡單方法之一。DataResponseDownloadResponse 都有 map砍濒、tryMap淋肾、mapErrortryMapError 方法,這些方法可以轉換響應爸邢,同時保留與響應相關聯(lián)的元數(shù)據樊卓。例如,可以使用 map 從可解碼響應中提取屬性杠河,同時還保留以前的任何解析錯誤碌尔。

AF.request(...).responseDecodable(of: SomeType.self) { response in
    let propertyResponse = response.map { $0.someProperty }

    debugPrint(propertyResponse)
}

引發(fā)錯誤的轉換也可以與 tryMap 一起使用,可能用于執(zhí)行驗證:

AF.request(..).responseDecodable(of: SomeType.self) { response in
    let propertyResponse = response.tryMap { try $0.someProperty.validated() }

    debugPrint(propertyResponse)
}

創(chuàng)建自定義響應序列化器

當 Alamofire 提供的 ResponseSerializer 或響應轉換不夠靈活券敌,或者定制量很大時唾戚,創(chuàng)建 ResponseSerializer 是封裝該邏輯的好方法。集成自定義 ResponseSerializer 通常有兩個部分:創(chuàng)建遵循協(xié)議的類型和擴展相關請求類型以方便使用待诅。例如叹坦,如果服務器返回了一個特殊編碼的 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 監(jiān)聽移動網絡和 WiFi 網絡接口的主機和地址的可達性變化。

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

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

一定要記住保存 manager荷逞,否則不會報告狀態(tài)更改媒咳。另外,不要將 scheme 包含在 host 字符串中种远,否則可達性將無法正常工作涩澡。

當使用網絡可達性來確定下一步要做什么時,需要記住一些重要的事情坠敷。

  • 不要使用可達性來確定是否應發(fā)送網絡請求妙同。
    • 你應該總是把請求發(fā)出去。
  • 恢復可訪問性后膝迎,使用事件重試失敗的網絡請求粥帚。
    • 盡管網絡請求可能仍然失敗,但現(xiàn)在是重試請求的好時機限次。
  • 網絡可達性狀態(tài)可用于確定網絡請求失敗的原因芒涡。
    • 如果網絡請求失敗,則更有用的方法是告訴用戶網絡請求由于離線而失敗卖漫,而不是更技術性的錯誤费尽,例如“請求超時”

或者衩椒,使用 RequestRetrier(如內置的 RetryPolicy)可能會更簡單脸甘、更可靠,而不是使用可訪問性更新來重試因網絡故障而失敗的請求耗溜。默認情況下突委,RetryPolicy 將在各種錯誤條件下重試等冪請求柏卤,包括離線的網絡連接。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末匀油,一起剝皮案震驚了整個濱河市缘缚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钧唐,老刑警劉巖忙灼,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匠襟,死亡現(xiàn)場離奇詭異钝侠,居然都是意外死亡该园,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門帅韧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來里初,“玉大人,你說我怎么就攤上這事忽舟∷粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵叮阅,是天一觀的道長刁品。 經常有香客問我,道長浩姥,這世上最難降的妖魔是什么挑随? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮勒叠,結果婚禮上兜挨,老公的妹妹穿的比我還像新娘。我一直安慰自己眯分,他們只是感情好拌汇,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弊决,像睡著了一般噪舀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丢氢,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天傅联,我揣著相機與錄音,去河邊找鬼疚察。 笑死蒸走,一個胖子當著我的面吹牛,可吹牛的內容都是我干的貌嫡。 我是一名探鬼主播比驻,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岛抄!你這毒婦竟也來了别惦?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤夫椭,失蹤者是張志新(化名)和其女友劉穎掸掸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡扰付,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年堤撵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羽莺。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡实昨,死狀恐怖,靈堂內的尸體忽然破棺而出盐固,到底是詐尸還是另有隱情荒给,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布刁卜,位于F島的核電站志电,受9級特大地震影響,放射性物質發(fā)生泄漏蛔趴。R本人自食惡果不足惜溪北,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夺脾。 院中可真熱鬧之拨,春花似錦、人聲如沸咧叭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菲茬。三九已至吉挣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婉弹,已是汗流浹背睬魂。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镀赌,地道東北人氯哮。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像商佛,于是被迫代替她去往敵國和親喉钢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容