原文: Alamofire 4.0 Migration Guide
做者: cnoon
譯者: kemchenjhtml
譯者注:最近打算把公司項目遷移到 Swift 3.0, 順手把 Alamofire 4.0 的遷移指南翻譯了, 以前雖然讀過一部分源碼, 但仍是看到了不少新東西, 新的 Adapter 和 Retrier 我都打算用到項目裏, 但願你們看完也可以有收穫.git
Alamofire 4.0 是 Alamofire 最新的一個大版本更新, 一個基於 Swift 的 iOS, tvOS, macOS, watchOS 的 HTTP 網絡庫. 做爲一個大版本更新, 就像語義上那樣, 4.0 的 API 引入了一些破壞性修改.github
這篇導引旨在幫助你們從 Alamofire 3.x 平滑過渡到最新版本, 同時也解釋一下新的設計和結構, 以及功能上的更新.swift
iOS 8.0+, macOS 10.10.0+, tvOS 9.0+ 以及 watchOS 2.0+api
Xcode 8.1+安全
Swift 3.0+服務器
那些想要在 iOS 8 或者 macOS 10.9 使用 Alamofire 的, 請使用 3.x 版本的最新 release(同時支持 Swift 2.2以及2.3)網絡
完美適配 Swift 3: 跟進了新的 API 設計規範.session
新的錯誤處理系統: 根據提案 SE-0112 裏的新模式, 新增了 AFError
類型.閉包
新的 RequestAdapter
協議: 能夠在初始化 Request
的時候進行快速便捷的適配, 例如在請求頭裏加入 Authorization
新的 RequestRetrier
協議: 能夠檢測而且重試失敗的 Request
, 甚至能夠本身根據一系列需求去構建一套驗證的解決方案( OAuth1, OAuth2, xAuth, Basic Auth 之類的).
新的 Parameter Encoding
協議: 取代掉以前的 ParameterEncoding
枚舉, 容許你更簡單的拓展和自定義, 而且在錯誤時拋出異常, 而不是簡單的返回一個元組.
新的請求類型: 包括 DataRequest
, DownloadRequest
, UploadRequest
和 StreamRequest
, 實現了特定的進度, 驗證和序列化的 API 以及各自的 Request
類型.
新的進度 API: 包括 downloadProgress
和 uploadProgress
, 支持 progress
和 Int64
類型, 而且會在指定的線程運行, 默認爲主線程.
更強大的數據驗證: 在驗證失敗的時候, 包括 data
或者 temporaryURL
和 destinationURL
均可以使用內聯的閉包去轉化服務器返回的錯誤信息
新的下載地址處理: 你能夠得到完整的控制權, 而不是像以前那樣只是提供一個 destinationURL
, 還得建立臨時文件夾, 刪掉以前的文件.
新的 Response
類型: 統一 response 的 API, 而且爲全部下載任務提供 temporaryURL
和 downloadURL
, 以及其它新平臺上的任務屬性.
Alamofire 4 跟進了 Swift 3 裏全部的修改, 包括 API 設計規範. 所以, 幾乎全部 Alamofire 的 API 都進行了必定程度的修改. 咱們沒辦法把這些修改所有在文檔裏列出來, 因此咱們會把最經常使用的那些 API 列出來, 而後告訴你們這些 API 進行了哪些修改, 而不是期望那些有時幫倒忙的編譯錯誤提示.
一些經常使用的類移到了全局命名空間成爲一級類, 讓他們更容易使用.
Manager
改成 SessionManager
Request.TaskDelegate
改成 TaskDelegate
Request.DataTaskDelegate
改成 DataTaskDelegate
Request.DownloadTaskDelegate
改成 DownloadTaskDelegate
Request.UploadTaskDelegate
改成 UploadTaskDelegate
咱們也從新調整了文件結構和組織模式, 幫助更好的跟進代碼. 咱們但願這可讓更多用戶去了解內部結構和 Alamofire 的具體實現. 只是就是力量.
生成請求是 Alamofire 裏最主要的操做, 這裏有 3.x 以及 4 的等效代碼對比.
// Alamofire 3 Alamofire.request(.GET, urlString).response { request, response, data, error in print(request) print(response) print(data) print(error) } // Alamofire 4 Alamofire.request(urlString).response { response in // 默認爲 `.get` 方法 debugPrint(response) }
// Alamofire 3 let parameters: [String: AnyObject] = ["foo": "bar"] Alamofire.request(.GET, urlString, parameters: parameters, encoding: .JSON) .progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)") } .validate { request, response in // 自定義的校驗閉包 (訪問不到服務器返回的數據) return .success } .responseJSON { response in debugPrint(response) } // Alamofire 4 let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default) .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in print("進度: \(progress.fractionCompleted)") } .validate { request, response, data in // 自定義的校驗閉包, 如今加上了 `data` 參數(容許你提早轉換數據以便在必要時挖掘到錯誤信息) return .success } .responseJSON { response in debugPrint(response) }
// Alamofire 3 let destination = DownloadRequest.suggestedDownloadDestination() Alamofire.download(.GET, urlString, destination: destination).response { request, response, data, error in // fileURL 在哪, 怎麼獲取? print(request) print(response) print(data) print(error) } // Alamofire 4 let destination = DownloadRequest.suggestedDownloadDestination() Alamofire.download(urlString, to: destination).response { response in // 默認爲 `.get` 方法 print(response.request) print(response.response) print(response.temporaryURL) print(response.destinationURL) print(response.error) }
// Alamofire 3 let destination = DownloadRequest.suggestedDownloadDestination() Alamofire.download(urlRequest, destination: destination).validate().responseData { response in // fileURL 在哪裏, 太難獲取了 debugPrint(response) } // Alamofire 4 Alamofire.download(urlRequest, to: destination).validate().responseData { response in debugPrint(response) print(response.temporaryURL) print(response.destinationURL) }
// Alamofire 3 let fileURL: NSURL let destination: Request.DownloadFileDestination = { _, _ in fileURL } let parameters: [String: AnyObject] = ["foo": "bar"] Alamofire.download(.GET, urlString, parameters: parameters, encoding: .JSON, to: destination) .progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)") } .validate { request, response in // 自定義的校驗實現(獲取不到臨時下載位置和目標下載位置) return .success } .responseJSON { response in print(fileURL) // 只有在閉包捕獲了的狀況才能獲取到, 不夠理想 debugPrint(response) } // Alamofire 4 let fileURL: URL let destination: DownloadRequest.DownloadFileDestination = { _, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } let parameters: Parameters = ["foo": "bar"] Alamofire.download(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default, to: destination) .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in print("進度: \(progress.fractionCompleted)") } .validate { request, response, temporaryURL, destinationURL in // 自定義的校驗閉包, 如今包含了 fileURL (必要時能夠獲取到錯誤信息) return .success } .responseJSON { response in debugPrint(response) print(response.temporaryURL) print(response.destinationURL) }
// Alamofire 3 Alamofire.upload(.POST, urlString, data: data).response { request, response, data, error in print(request) print(response) print(data) print(error) } // Alamofire 4 Alamofire.upload(data, to: urlString).response { response in // 默認爲 `.post` 方法 debugPrint(response) }
// Alamofire 3 Alamofire.upload(urlRequest, file: fileURL).validate().responseData { response in debugPrint(response) } // Alamofire 4 Alamofire.upload(fileURL, with: urlRequest).validate().responseData { response in debugPrint(response) }
// Alamofire 3 Alamofire.upload(.PUT, urlString, file: fileURL) .progress { bytes, totalBytes, totalBytesExpected in // 這裏的進度是上傳仍是下載的? print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)") } .validate { request, response in // 自定義的校驗實現(獲取不到服務端的數據) return .success } .responseJSON { response in debugPrint(response) } // Alamofire 4 Alamofire.upload(fileURL, to: urlString, method: .put) .uploadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in print("上傳進度: \(progress.fractionCompleted)") } .downloadProgress { progress in // 默認在主隊列調用 print("下載進度: \(progress.fractionCompleted)") } .validate { request, response, data in // 自定義的校驗閉包, 如今加上了 `data` 參數(容許你提早轉換數據以便在必要時挖掘到錯誤信息) return .success } .responseJSON { response in debugPrint(response) }
就像你看到的, 有不少 API 破壞性的修改, 但經常使用的 API 仍是沿用了原來的設計, 但如今可以經過一行代碼去生成更多更復雜的請求, 保持秩序的同時更加簡潔.
URLStringConvertible
協議有兩個很小的改變.
第一個沒什麼了不得的"大"改變就是 URLStringConvertible
已經被重命名爲 URLConvertible
. 在 3.x 裏, URLStringConvertible
的定義是這樣子的:
public protocol URLStringConvertible { var URLString: String { get } }
如今在 Alamofire 4 裏, URLConvertible
協議是這樣定義的:
public protocol URLConvertible { func asURL() throws -> URL }
就像你看到的, URLString
屬性徹底去掉了, 而後換成了可能會拋出異常的 asURL
方法. 爲了解釋這樣作的緣由, 咱們先回顧一下.
Alamofire 一個最最多見的問題就是用戶忘了對 URL 進行百分號編碼, 致使 Alamofire 崩潰掉. 直到如今, 咱們(Alamofire 團隊)的態度都是 Alamofire 就是這麼設計的, 而你的 URL 必須遵照 RFC 2396 協議. 但這對於社區來講並不那麼好, 由於咱們更但願 Alamofire 告訴咱們的 URL 是不合法的而不是直接 crash 掉.
如今, 回到新的 URLConvertible
協議. Alamofire 之因此不能安全地處理不合規範的 URL 字符串, 事實上是由於 URLStringConvertible
安全性的缺失. Alamofire 不可能知道你是怎麼造出一個不合法的 URL. 因此, 若是 URL
不能統統過 URLConvertible
被建立的話, 一個 AFError.invalidURL
的異常就會被拋出.
這個修改(以及其它不少修改都)可讓 Alamofire 安全地處理不合理的 URL, 而且會在回調裏拋出異常.
URLRequest
再也不遵照 URLStringConvertible
, 如今是 URLConvertible
. 但這也只是以前版本的一個延展而已, 並不那麼重要. 不過這極可能會讓 Alamofire 的 API 產生歧義. 所以, URLRequest
再也不遵照 URLStringConvertible
.
這意味着你不能在代碼裏像這樣子作了:
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let urlString = urlRequest.urlString
在 Alamofire 4裏, 你應該這麼作:
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!) let urlString = urlRequest.url?.absoluteString
查看 PR-1505 以獲取更多信息.
在 3.x 裏, URLRequestConvertible
也會產生相同的歧義問題, 以前的 URLRequestConvertible
是這麼定義的:
public protocol URLRequestConvertible { var URLRequest: URLRequest { get } }
如今, 在 Alamofire 4 裏, 變成了這樣子:
public protocol URLRequestConvertible { func asURLRequest() throws -> URLRequest }
就像看到的這樣, URLRequest
屬性被替換成了 asURLRequest
方法, 而且在生成 URLRequest
失敗時會拋出異常.
這影響最大的多是採用了 Router
(路由)設計的你, 若是你用了 Router
, 那你就不得不去改變, 但會變得更好! 你須要去實現 asURLRequest
方法, 在必要的時候會拋出異常. 你再也不須要強制解包數據和參數, 或者在 do-catch 裏構建一個 ParameterEncoding
. 如今 Router
拋出的任何錯誤均可以由 Alamofire 幫你處理掉.
查看 PR-1505 以獲取更多信息.
RequestAdapter
協議是 Alamofire 4 裏的全新功能.
public protocol RequestAdapter { func adapt(_ urlRequest: URLRequest) throws -> URLRequest }
它可讓每個 SessionManager
生成的 Request
都在生成以前被解析而且按照規則適配. 一個使用適配器很典型的場景就是給請求添加一個 Authorization
的請求頭.
class AccessTokenAdapter: RequestAdapter { private let accessToken: String init(accessToken: String) { self.accessToken = accessToken } func adapt(_ urlRequest: URLRequest) throws -> URLRequest { var urlRequest = urlRequest if urlRequest.urlString.hasPrefix("https://httpbin.org") { urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization") } return urlRequest } } let sessionManager = SessionManager() sessionManager.adapter = AccessTokenAdapter(accessToken: "1234") sessionManager.request("https://httpbin.org/get")
若是一個 Error
在適配過程當中產生的話, 它會逐層拋出, 最後傳遞到 Request
的請求回調裏.
查看 PR-1450 獲取更多信息.
RequestRetrier
是 Alamofire 4 的另外一個全新協議.
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void public protocol RequestRetrier { func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) }
它能夠在 Request
遇到 Error
的時候, 在指定的延遲以後從新發起.
class OAuth2Handler: RequestAdapter, RequestRetrier { public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: RequestRetryCompletion) { if let response = request.task.response as? HTTPURLResponse, response.statusCode == 401 { completion(true, 1.0) // 1秒後重試 } else { completion(false, 0.0) // 不重連 } } } let sessionManager = SessionManager() sessionManager.retrier = OAuth2Handler() sessionManager.request(urlString).responseJSON { response in debugPrint(response) }
重連器可讓你在檢測到 Request
完成而且完成全部 Validation
檢測以後再考慮是否重試. 當 RequestAdapter
和 RequestRetrier
一塊兒使用的時候, 你能夠給 OAuth1, OAuth2, Basic Auth 建立一套持續更新的校驗系統(credential refresh systems), 甚至是快速重試的策略. 可能性是無限的. 想要獲取更多關於這個話題的信息和例子, 請查看 README.
譯者注: 這裏沒太能理解做者的意思, 翻譯得很差, 直接放原文:
When using both theRequestAdapter
andRequestRetrier
protocols together, you can create credential refresh systems for OAuth1, OAuth2, Basic Auth and even exponential backoff retry policies.
在 iOS, tvOS 10 和 macOS 10.12 裏, 蘋果引入了新的 URLSessionTaskMetrics API, task metrics 包含了一些 request 和 response 的統計信息, API 跟 Alamofire 的 Timeline
很像, 但提供了許多 Alamofire 裏獲取不到的統計信息. 咱們對這些新的 API 特別興奮, 但把這些所有都暴露到每個 Response
類型裏意味着這並不容易使用.
Alamofire.request(urlString).response { response in debugPrint(response.metrics) }
有一點很重要的是, 這些 API 只有在 iOS 和 tvOS 10+ 和 macOS 10.12+上才能使用. 因此它是依賴於運行設備的, 你可能須要作可行性檢查.
Alamofire.request(urlString).response { response in if #available(iOS 10.0, *) { debugPrint(response.metrics) } }
查看 PR-1492 獲取更多信息.
Alamofire 4 增強了現有的功能而且加入了不少新功能. 這一章節主要是大概地過一遍功能的更新和使用方式. 若是想要獲取更多相關信息, 請點進連接查看相關的 pull request.
Alamofire 4 加入了全新的異常系統, 採用了提案 SE-0112 裏提出的新模式. 新的異常系統主要圍繞 AFError
, 一個繼承了 Error
的枚舉類型, 包含四個主要的 case.
.invalidURL(url: URLConvertible)
- 建立 URL
失敗的時候返回一個 URLConvertible
類型的值
.parameterEncodingFailed(reason: ParameterEncodingFailureReason)
- 當其中一個參數編碼出錯的時候就會拋出錯誤並返回
.multipartEncodingFailed(reason: MultipartEncodingFailureReason)
- multipart 編碼出錯就會拋出錯誤並返回
.responseValidationFailed(reason: ResponseValidationFailureReason)
- 當調用 validate()
拋出錯誤時捕獲而後拋出到外部.
.responseSerializationFailed(reason: ResponseSerializationFailureReason)
- 返回的數據序列化出錯時會拋出異常並返回.
每個 case 都包含了特定的異常理由, 而且異常理由又是另外一個帶有具體錯誤信息的枚舉類型. 這會讓 Alamofire 更容易識別出錯誤的來源和緣由.
Alamofire.request(urlString).responseJSON { response in guard case let .failure(error) = response.result else { return } if let error = error as? AFError { switch error { case .invalidURL(let url): print("無效 URL: \(url) - \(error.localizedDescription)") case .parameterEncodingFailed(let reason): print("參數編碼失敗: \(error.localizedDescription)") print("失敗理由: \(reason)") case .multipartEncodingFailed(let reason): print("Multipart encoding 失敗: \(error.localizedDescription)") print("失敗理由: \(reason)") case .responseValidationFailed(let reason): print("Response 校驗失敗: \(error.localizedDescription)") print("失敗理由: \(reason)") switch reason { case .dataFileNil, .dataFileReadFailed: print("沒法讀取下載文件") case .missingContentType(let acceptableContentTypes): print("文件類型不明: \(acceptableContentTypes)") case .unacceptableContentType(let acceptableContentTypes, let responseContentType): print("文件類型: \(responseContentType) 沒法讀取: \(acceptableContentTypes)") case .unacceptableStatusCode(let code): print("請求返回狀態碼出錯: \(code)") } case .responseSerializationFailed(let reason): print("請求返回內容序列化失敗: \(error.localizedDescription)") print("失敗理由: \(reason)") } print("錯誤: \(error.underlyingError)") } else if let error = error as? URLError { print("URL 錯誤: \(error)") } else { print("未知錯誤: \(error)") } }
新的設計給你的處理方式更多的自由, 能夠在你須要的時候深刻到最具體的 error. 這也會讓本來要四處應對 NSError
的開發者更加輕鬆地完成工做. 在 Alamofire 裏經過使用自定義的 Error
類型, 咱們能夠看到 Result
和 Response
的泛型參數縮減到了只有一個, 簡化了返回數據序列化的邏輯.
查看 PR-1419 獲取更多信息.
ParameterEncoding
枚舉類型在過去兩年很好地解決了問題. 但咱們在 Alamofire 4 裏想要定位的時候卻感受到了一些侷限.
.url
總讓人有點迷惑, 由於它是一個 HTTP 協議定義的地址
.urlEncodedInURL
跟 .url
老是會混淆起來, 讓人分不清它們行爲的區別
.JSON
和 .PropertyList
編碼不能自定義編碼格式或者寫入的方式
.Custom
編碼對於用戶來講太難掌握
由於這些緣由, 咱們決定在 Alamofire 4 把這個枚舉去掉! 如今, ParameterEncoding
變成了一個協議, 加入了 Parameters
的類型別名去建立你的參數字典, 而且經過遵照這個協議創建了三個編碼結構體 URLEncoding
, JSONEncoding
和 PropertyList
.
public typealias Parameters = [String: Any] public protocol ParameterEncoding { func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest }
新的 URLEncoding
結構體包含了一個 Destination
枚舉, 支持三種類型的目標編碼
.methodDependent
- 對於 GET
, HEAD
和 DELETE
方法使用 query 字符串, 而別的 HTTP 方法則會編碼爲 HTTP body.
.queryString
- 設置或者往現有的 queryString 裏增長內容
.httpBody
- 設置請求的 HTTP body 內容
這些目標編碼格式會讓你更容易控制 URLRequest
的參數編碼方式. 建立請求依舊使用和以前同樣的方式, 無論編碼的形式怎樣, 都會保持與以前同樣的默認行爲.
let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString, parameters: parameters) // Encoding => URLEncoding(destination: .methodDependent) Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .queryString)) Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding(destination: .httpBody)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的靜態屬性 (咱們想鼓勵你們使用這種更簡潔的形式) Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.default) Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.queryString) Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.httpBody)
新的 JSONEncoding
結構體開放了讓你自定義 JSON 寫入形式的接口.
let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: [])) Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding(options: .prettyPrinted)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的靜態屬性 (咱們想鼓勵你們使用這種更簡潔的形式) Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.default) Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.prettyPrinted)
新的 PropertyListEncoding
結構體容許自定義 plist 的格式和寫入選項
let parameters: Parameters = ["foo": "bar"] Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding(format: .xml, options: 0)) Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding(format: .binary, options: 0)) // Static convenience properties (we'd like to encourage everyone to use this more concise form) // 便利的靜態屬性 (咱們想鼓勵你們使用這種更簡潔的形式) Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.xml) Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.binary)
創建一個自定義的 ParameterEncoding
只要遵照這個協議創建類型便可. 想要獲取更多相關例子, 請查看下面的 README
查看 PR-1465 獲取更多信息
在 Alamofire 4, request
, download
, upload
和 stream
的 API 不會再返回 Request
, 他們會返回特定的 Request
子類. 有下面幾個引導咱們作出這個改變的現實緣由和社區的疑問:
Progress: progress
方法的行爲會在 upload 請求裏會很容易讓人迷惑.
progress
在一個 upload 請求裏返回的是什麼? 上傳的進度? 仍是返回內容的下載進度?
若是都返回, 那咱們怎麼區分他們, 在何時能知道是到底返回的是哪個?
Response Serializers: 返回內容的序列化是爲了 data 和 upload 請求設計的, donwload 和 stream 請求並不須要序列化.
你要怎麼才能在下載完成時獲取到文件的地址?
responseData
, responseString
和 responseJSON
對於一個 donwload 請求來講意味着什麼? stream 請求呢?
Alamofire 4 如今有四個 Request
的子類, 而且每一個字類都有一些特有的 API. 這樣就可讓每個子類可以經過創建 extension 來定製特定類型的請求.
open class Request { // 包含了共有的屬性, 驗證, 和狀態方法 // 遵照 CustomStringConvertible 和 CustomDebugStringConvertible } open class DataRequest: Request { // 包含了數據流(不要跟 StreamRequest 混淆)和下載進度的方法 } open class DownloadRequest: Request { // 包含了下載位置和選項, 已下載的數據以及進度方法 } open class UploadRequest: DataRequest { // 繼承了全部 DataRequest 的方法, 而且包含了上傳進度的方法 } open class StreamRequest: Request { // 只繼承了 Request, 目前暫時沒有任何自定義的 API }
經過這樣的切分, Alamofire 如今能夠爲每個類型的請求自定義相關的 API. 這會覆蓋到全部可能的需求, 但讓咱們花點時間來仔細瞭解一下這會如何改變進度彙報和下載地址.
查看 PR-1455 獲取更多信息
Data, download 和 upload 請求的進度彙報系統徹底從新設計了一遍. 每個請求類型都包含有一個閉包, 每當進度更新的時候, 就會調用閉包而且傳入 Progress
類型的參數. 這個閉包會在指定的隊列被調用, 默認爲主隊列.
Data Request 進度
Alamofire.request(urlString) .downloadProgress { progress in // 默認在主隊列調用 print("下載進度: \(progress.fractionCompleted)") } .responseJSON { response in debugPrint(response) }
Download Request 進度
Alamofire.download(urlString, to: destination) .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in // 在 .utility 隊列裏調用 print("下載進度: \(progress.fractionCompleted)") } .responseJSON { response in debugPrint(response) }
Upload Request 進度
Alamofire.upload(data, to: urlString, withMethod: .post) .uploadProgress { progress in // 默認在主隊列調用 print("上傳進度: \(progress.fractionCompleted)") } .downloadProgress { progress in // 默認在主隊列調用 print("下載進度: \(progress.fractionCompleted)") } .responseData { response in debugPrint(response) }
如今很容易就能夠區分開 upload request 裏的上傳和下載進度.
查看 PR-1455 獲取更多信息.
在 Alamofire 3.x, 順利完成的 download requests 老是會在 destination
回調裏把臨時文件移動到最終目標文件夾裏. 這很方便, 但也同時帶來了幾個限制:
Forced
- API 強制你去提供一個 destination 閉包來移動文件, 即便你驗證事後不想移動文件了.
Limiting
- 沒有任何方式能夠去調整文件系統移動文件的優先級別.
若是你須要在移動到目標文件夾以前刪掉以前存在的文件呢?
若是你須要在移動臨時文件以前建立目錄呢?
這些限制都會在 Alamofire 4 裏都不復存在. 首先是 optional 的 destination 閉包. 如今, destination
默認爲 nil, 意味着文件系統不會移動文件, 而且會返回臨時文件的 URL.
Alamofire.download(urlString).responseData { response in print("臨時文件的 URL: \(response.temporaryURL)") }
咱們將會恢復
DownloadResponse
類型, 更多詳細信息請查看 Reponse Serializers 章節.
另一個主要的改變是 destination 閉包裏面加上了下載選項, 讓你能夠進行更多文件系統操做. 爲了達到目的, 咱們創建了一個 DownloadOptions
類型而且添加到 DownloadFileDestination
閉包裏.
public typealias DownloadFileDestination = ( _ temporaryURL: URL, _ response: HTTPURLResponse) -> (destinationURL: URL, options: DownloadOptions)
現階段支持的兩個 DownloadOptions
是:
.createIntermediateDirectories
- 若是有指定的下載地址的話, 會爲下載地址建立相應的目錄
.removePreviousFile
- 若是有指定的下載地址的話, 會自動替代掉同名文件
這兩個選項能夠像下面這樣用:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) } Alamofire.download(urlString, to: destination).response { response in debugPrint(response) }
若是一個異常在文件系統操做時拋出的話, DownloadResponse
的 error
就會是 URLError
類型.
查看 PR-1462 獲取更多信息.
在 Alamofire 4 裏有幾個能夠增強數據驗證系統的地方. 包括了:
Validation
回調閉包裏傳入的 data
Request
子類能夠自定義數據驗證系統, 例如 download 請求裏的 temporaryURL
和 destinationURL
暴露到了回調閉包裏
經過繼承 Request
, 每個 Request
的子類均可以自定義一套數據驗證的閉包(typealias)和請求的 API.
DataRequest
(UploadRequest
的父類)暴露出來的 Validation
目前是這樣定義的:
extension DataRequest { public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult }
直接在閉包裏把 Data?
暴露出來, 你就不須要再給 Request
增長一個 extension 去訪問這個屬性了. 如今你能夠直接這樣子作:
Alamofire.request(urlString) .validate { request, response, data in guard let data = data else { return .failure(customError) } // 1) 驗證返回的數據保證接下來的操做不會出錯 // 2) 若是驗證失敗, 你能夠把錯誤信息返回出去, 甚至加上自定義的 error return .success } .response { response in debugPrint(response) }
DownloadRequest
裏的 Validation
閉包跟 DataRequest
裏的很像, 但爲了下載任務作了更多的定製.
extension DownloadRequest { public typealias Validation = ( _ request: URLRequest?, _ response: HTTPURLResponse, _ temporaryURL: URL?, _ destinationURL: URL?) -> ValidationResult }
temporaryURL
和 destinationURL
參數如今讓你能夠在閉包內直接獲取到服務器返回的數據. 這可讓你校驗下載好的文件, 在有須要的時候能夠拋出一個自定義的錯誤.
Alamofire.download(urlString) .validate { request, response, temporaryURL, destinationURL in guard let fileURL = temporaryURL else { return .failure(customError) } do { let _ = try Data(contentsOf: fileURL) return .success } catch { return .failure(customError) } } .response { response in debugPrint(response) }
經過直接在閉包裏暴露服務器返回的數據, 這裏面的全部異常均可以在 Validation
閉包裏捕獲到, 而且能夠自定義錯誤信息. 若是這裏獲取到的信息和 response 序列化回調裏同樣的話, response 能夠用來處理錯誤信息而不是簡單地把邏輯賦值過來. 具體的例子, 請查看下面的 README.
查看 PR-1461 獲取更多信息.
Alamofire 3.x 裏的序列化系統有這麼幾個限制:
序列化的 API 能夠用在 download 和 stream 請求裏, 但卻會致使未知的行爲發生
怎麼在下載成功時獲取到文件 URL?
responseData
, responseString
或者 responseJSON
會在 donwload 請求裏產生怎樣的行爲? stream 請求呢?
response
API 返回四個參數而不是封裝到一個 Response
類型裏.
最大的問題是 API 任何改變都會致使前面行爲的變化.
在序列化和反序列化的 API 之間切換會讓人迷惑, 同時致使難以 debug 的編譯錯誤.
就像你看到的, Alamofire 3.x 的這一套序列化系統有這麼多限制. 因此, 在 Alamofire 4裏, Request
類型首先被切分到各個子類裏, 這麼作給自定義序列化方式, 和自定義 API 留下了空間. 在咱們更深刻了解序列化方式以前, 咱們先了解一下新的 Response
類型
DefaultDataResponse
表明了未被序列化的服務器返回數據. Alamofire 沒有作任何處理過的, 只是純粹地從 SessionDelegate
裏獲取信息而且包裝在一個結構體裏面返回.
public struct DefaultDataResponse { public let request: URLRequest? public let response: HTTPURLResponse? public let data: Data? public let error: Error? public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } }
下面是你會得到 DataRequest.response
的一種返回.
Alamofire.request(urlString).response { response in debugPrint(response) } Alamofire.upload(file, to: urlString).response { response in debugPrint(response) }
泛型 DataResponse
類型跟 Alamofire 3.x 裏的 Response
同樣, 但內部重構而且包含了新的 metrics
變量.
public struct DataResponse<Value> { public let request: URLRequest? public let response: HTTPURLResponse? public let data: Data? public let result: Result<Value> public let timeline: Timeline public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } }
使用 DataRequest
和 UploadRequest
, 你能夠像以前(3.x)那樣使用 response 序列化的 API
Alamofire.request(urlString).responseJSON { response in debugPrint(response) print(response.result.isSuccess) } Alamofire.upload(fileURL, to: urlString).responseData { response in debugPrint(response) print(response.result.isSuccess) }
由於 donwload 請求跟 data 和 upload 請求很不同, 因此 Alamofire 4 包含了自定義的 donwload Response
類型. DefaultDownloadResponse
類型表明未序列化的返回數據, 包含了全部 SessionDelegate
信息的結構體.
public struct DefaultDownloadResponse { public let request: URLRequest? public let response: HTTPURLResponse? public let temporaryURL: URL? public let destinationURL: URL? public let resumeData: Data? public let error: Error? public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics } }
DefaultDownloadResponse
類型在使用新的 DownloadRequest.response
API 時就會被返回.
Alamofire.download(urlString).response { response in debugPrint(response) print(response.temporaryURL) }
新的泛型 DownloadResponse
跟 DataResponse
很像, 但包含了 download 請求特有的信息. DownloadResponse
類型在使用 DownloadRequest
時就會被返回. 這些新的 API 一樣也適用於 DataRequest
, 同樣可以獲取臨時目錄的 url 和目標目錄的 url.
Alamofire.download(urlString, to: destination) .responseData { response in debugPrint(response) } .responseString { response in debugPrint(response) } .responseJSON { response in debugPrint(response) } .responsePropertyList { response in debugPrint(response) }
新的序列化 API 讓文件下載和序列化更加容易完成.
若是你已經建立了自定義的序列化, 你也許會想要拓展支持 data 和 download 請求, 就像咱們在 Alamofire 序列化 API 裏面作的同樣.. 若是你決定這麼作, 能夠仔細看一下 Alamofire 怎麼在幾種 Request
類型裏共享序列化方法, 而後把實現寫到 Request
裏就能夠了. 這可讓咱們 DRY up 邏輯而且避免重複的代碼.(Don't repeat yourself)
查看 PR-1457 獲取更多信息.