原文: Alamofire 4.0 Migration Guide
作者: cnoon
譯者: kemchenj
譯者注:
最近打算把公司項目遷移到 Swift 3.0, 順手把 Alamofire 4.0 的遷移指南翻譯了, 之前雖然讀過一部分源碼, 但還是看到了很多新東西, 新的 Adapter 和 Retrier 我都打算用到項目里, 希望大家看完也能夠有收獲.
正文:
Alamofire 4.0 是 Alamofire 最新的一個大版本更新, 一個基于 Swift 的 iOS, tvOS, macOS, watchOS 的 HTTP 網(wǎng)絡(luò)庫. 作為一個大版本更新, 就像語義上那樣, 4.0 的 API 引入了一些破壞性修改.
這篇導(dǎo)引旨在幫助大家從 Alamofire 3.x 平滑過渡到最新版本, 同時也解釋一下新的設(shè)計和結(jié)構(gòu), 以及功能上的更新.
要求
- iOS 8.0+, macOS 10.10.0+, tvOS 9.0+ 以及 watchOS 2.0+
- Xcode 8.1+
- Swift 3.0+
那些想要在 iOS 8 或者 macOS 10.9 使用 Alamofire 的, 請使用 3.x 版本的最新 release(同時支持 Swift 2.2以及2.3)
升級的好處
- 完美適配 Swift 3: 跟進(jìn)了新的 API 設(shè)計規(guī)范.
-
新的錯誤處理系統(tǒng): 根據(jù)提案 SE-0112 里的新模式, 新增了
AFError
類型. -
新的
RequestAdapter
協(xié)議: 可以在初始化Request
的時候進(jìn)行快速便捷的適配, 例如在請求頭里加入Authorization
-
新的
RequestRetrier
協(xié)議: 可以檢測并且重試失敗的Request
, 甚至可以自己根據(jù)一系列需求去構(gòu)建一套驗證的解決方案( OAuth1, OAuth2, xAuth, Basic Auth 之類的). -
新的
Parameter Encoding
協(xié)議: 取代掉之前的ParameterEncoding
枚舉, 允許你更簡單的拓展和自定義, 并且在錯誤時拋出異常, 而不是簡單的返回一個元組. -
新的請求類型: 包括
DataRequest
,DownloadRequest
,UploadRequest
和StreamRequest
, 實現(xiàn)了特定的進(jìn)度, 驗證和序列化的 API 以及各自的Request
類型. -
新的進(jìn)度 API: 包括
downloadProgress
和uploadProgress
, 支持progress
和Int64
類型, 并且會在指定的線程運行, 默認(rèn)為主線程. -
更強(qiáng)大的數(shù)據(jù)驗證: 在驗證失敗的時候, 包括
data
或者temporaryURL
和destinationURL
都可以使用內(nèi)聯(lián)的閉包去轉(zhuǎn)化服務(wù)器返回的錯誤信息 -
新的下載地址處理: 你可以獲得完整的控制權(quán), 而不是像之前那樣只是提供一個
destinationURL
, 還得創(chuàng)建臨時文件夾, 刪掉之前的文件. -
新的
Response
類型: 統(tǒng)一 response 的 API, 并且為所有下載任務(wù)提供temporaryURL
和downloadURL
, 以及其它新平臺上的任務(wù)屬性.
API 破壞性的修改
Alamofire 4 跟進(jìn)了 Swift 3 里所有的修改, 包括 API 設(shè)計規(guī)范. 因此, 幾乎所有 Alamofire 的 API 都進(jìn)行了一定程度的修改. 我們沒辦法把這些修改全部在文檔里列出來, 所以我們會把最常用的那些 API 列出來, 然后告訴大家這些 API 進(jìn)行了哪些修改, 而不是指望那些有時幫倒忙的編譯錯誤提示.
命名空間的修改
一些常用的類移到了全局命名空間成為一級類, 讓他們更容易使用.
-
Manager
改為SessionManager
-
Request.TaskDelegate
改為TaskDelegate
-
Request.DataTaskDelegate
改為DataTaskDelegate
-
Request.DownloadTaskDelegate
改為DownloadTaskDelegate
-
Request.UploadTaskDelegate
改為UploadTaskDelegate
我們也重新調(diào)整了文件結(jié)構(gòu)和組織模式, 幫助更好的跟進(jìn)代碼. 我們希望這可以讓更多用戶去了解內(nèi)部結(jié)構(gòu)和 Alamofire 的具體實現(xiàn). 只是就是力量.
生成請求
生成請求是 Alamofire 里最主要的操作, 這里有 3.x 以及 4 的等效代碼對比.
Data Request - Simple with URL string
// 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 // 默認(rèn)為 `.get` 方法
debugPrint(response)
}
Data Request - Complex with URL string
// 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
// 自定義的校驗閉包 (訪問不到服務(wù)器返回的數(shù)據(jù))
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("進(jìn)度: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// 自定義的校驗閉包, 現(xiàn)在加上了 `data` 參數(shù)(允許你提前轉(zhuǎn)換數(shù)據(jù)以便在必要時挖掘到錯誤信息)
return .success
}
.responseJSON { response in
debugPrint(response)
}
Download Request - Simple With URL string
// 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 // 默認(rèn)為 `.get` 方法
print(response.request)
print(response.response)
print(response.temporaryURL)
print(response.destinationURL)
print(response.error)
}
Download Request - Simple With URLRequest
// 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)
}
Download Request - Complex With URL String
// 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
// 自定義的校驗實現(xiàn)(獲取不到臨時下載位置和目標(biāo)下載位置)
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("進(jìn)度: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// 自定義的校驗閉包, 現(xiàn)在包含了 fileURL (必要時可以獲取到錯誤信息)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL)
print(response.destinationURL)
}
Upload Request - Simple With URL string
// 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 // 默認(rèn)為 `.post` 方法
debugPrint(response)
}
Upload Request - Simple With URLRequest
// 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)
}
Upload Request - Complex With URL string
// Alamofire 3
Alamofire.upload(.PUT, urlString, file: fileURL)
.progress { bytes, totalBytes, totalBytesExpected in
// 這里的進(jìn)度是上傳還是下載的?
print("Bytes: \(bytesRead), Total Bytes: \(totalBytesRead), Total Bytes Expected: \(totalBytesExpectedToRead)")
}
.validate { request, response in
// 自定義的校驗實現(xiàn)(獲取不到服務(wù)端的數(shù)據(jù))
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("上傳進(jìn)度: \(progress.fractionCompleted)")
}
.downloadProgress { progress in // 默認(rèn)在主隊列調(diào)用
print("下載進(jìn)度: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// 自定義的校驗閉包, 現(xiàn)在加上了 `data` 參數(shù)(允許你提前轉(zhuǎn)換數(shù)據(jù)以便在必要時挖掘到錯誤信息)
return .success
}
.responseJSON { response in
debugPrint(response)
}
就像你看到的, 有很多 API 破壞性的修改, 但常用的 API 還是沿用了原來的設(shè)計, 但現(xiàn)在能夠通過一行代碼去生成更多更復(fù)雜的請求, 保持秩序的同時更加簡潔.
URLStringConvertible 協(xié)議
URLStringConvertible
協(xié)議有兩個很小的改變.
URLConvertible
第一個沒什么了不起的"大"改變就是 URLStringConvertible
已經(jīng)被重命名為 URLConvertible
. 在 3.x 里, URLStringConvertible
的定義是這樣子的:
public protocol URLStringConvertible {
var URLString: String { get }
}
現(xiàn)在在 Alamofire 4 里, URLConvertible
協(xié)議是這樣定義的:
public protocol URLConvertible {
func asURL() throws -> URL
}
就像你看到的, URLString
屬性完全去掉了, 然后換成了可能會拋出異常的 asURL
方法. 為了解釋這樣做的原因, 我們先回顧一下.
Alamofire 一個最最常見的問題就是用戶忘了對 URL 進(jìn)行百分號編碼, 導(dǎo)致 Alamofire 崩潰掉. 直到現(xiàn)在, 我們(Alamofire 團(tuán)隊)的態(tài)度都是 Alamofire 就是這么設(shè)計的, 而你的 URL 必須遵守 RFC 2396 協(xié)議. 但這對于社區(qū)來說并不那么好, 因為我們更希望 Alamofire 告訴我們的 URL 是不合法的而不是直接 crash 掉.
現(xiàn)在, 回到新的 URLConvertible
協(xié)議. Alamofire 之所以不能安全地處理不合規(guī)范的 URL 字符串, 事實上是因為 URLStringConvertible
安全性的缺失. Alamofire 不可能知道你是怎么造出一個不合法的 URL. 所以, 如果 URL
不能通通過 URLConvertible
被創(chuàng)建的話, 一個 AFError.invalidURL
的異常就會被拋出.
這個修改(以及其它很多修改都)可以讓 Alamofire 安全地處理不合理的 URL, 并且會在回調(diào)里拋出異常.
URLRequest Conformance
URLRequest
不再遵守 URLStringConvertible
, 現(xiàn)在是 URLConvertible
. 但這也只是之前版本的一個延展而已, 并不那么重要. 不過這很可能會讓 Alamofire 的 API 產(chǎn)生歧義. 因此, URLRequest
不再遵守 URLStringConvertible
.
這意味著你不能在代碼里像這樣子做了:
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!)
let urlString = urlRequest.urlString
在 Alamofire 4里, 你應(yīng)該這么做:
let urlRequest = URLRequest(url: URL(string: "https://httpbin.org/get")!)
let urlString = urlRequest.url?.absoluteString
查看 PR-1505 以獲取更多信息.
URLRequestConvertible
在 3.x 里, URLRequestConvertible
也會產(chǎn)生相同的歧義問題, 之前的 URLRequestConvertible
是這么定義的:
public protocol URLRequestConvertible {
var URLRequest: URLRequest { get }
}
現(xiàn)在, 在 Alamofire 4 里, 變成了這樣子:
public protocol URLRequestConvertible {
func asURLRequest() throws -> URLRequest
}
就像看到的這樣, URLRequest
屬性被替換成了 asURLRequest
方法, 并且在生成 URLRequest
失敗時會拋出異常.
這影響最大的可能是采用了 Router
(路由)設(shè)計的你, 如果你用了 Router
, 那你就不得不去改變, 但會變得更好! 你需要去實現(xiàn) asURLRequest
方法, 在必要的時候會拋出異常. 你不再需要強(qiáng)制解包數(shù)據(jù)和參數(shù), 或者在 do-catch 里構(gòu)建一個 ParameterEncoding
. 現(xiàn)在 Router
拋出的任何錯誤都可以由 Alamofire 幫你處理掉.
查看 PR-1505 以獲取更多信息.
新功能
Request Adapter (請求適配器)
RequestAdapter
協(xié)議是 Alamofire 4 里的全新功能.
public protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
它可以讓每一個 SessionManager
生成的 Request
都在生成之前被解析并且按照規(guī)則適配. 一個使用適配器很典型的場景就是給請求添加一個 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
在適配過程中產(chǎn)生的話, 它會逐層拋出, 最后傳遞到 Request
的請求回調(diào)里.
查看 PR-1450 獲取更多信息.
Request Retrier (請求重連)
RequestRetrier
是 Alamofire 4 的另一個全新協(xié)議.
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
的時候, 在指定的延遲之后重新發(fā)起.
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
檢測之后再考慮是否重試. 當(dāng) RequestAdapter
和 RequestRetrier
一起使用的時候, 你可以給 OAuth1, OAuth2, Basic Auth 創(chuàng)建一套持續(xù)更新的校驗系統(tǒng)(credential refresh systems), 甚至是快速重試的策略. 可能性是無限的. 想要獲取更多關(guān)于這個話題的信息和例子, 請查看 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.
Task Metrics
在 iOS, tvOS 10 和 macOS 10.12 里, 蘋果引入了新的 URLSessionTaskMetrics API, task metrics 包含了一些 request 和 response 的統(tǒng)計信息, API 跟 Alamofire 的 Timeline
很像, 但提供了許多 Alamofire 里獲取不到的統(tǒng)計信息. 我們對這些新的 API 特別興奮, 但把這些全部都暴露到每一個 Response
類型里意味著這并不容易使用.
Alamofire.request(urlString).response { response in
debugPrint(response.metrics)
}
有一點很重要的是, 這些 API 只有在 iOS 和 tvOS 10+ 和 macOS 10.12+上才能使用. 所以它是依賴于運行設(shè)備的, 你可能需要做可行性檢查.
Alamofire.request(urlString).response { response in
if #available(iOS 10.0, *) {
debugPrint(response.metrics)
}
}
查看 PR-1492 獲取更多信息.
Updated Features 更新的功能
Alamofire 4 加強(qiáng)了現(xiàn)有的功能并且加入了很多新功能. 這一章節(jié)主要是大概地過一遍功能的更新和使用方式. 如果想要獲取更多相關(guān)信息, 請點進(jìn)鏈接查看相關(guān)的 pull request.
Errors 異常
Alamofire 4 加入了全新的異常系統(tǒng), 采用了提案 SE-0112 里提出的新模式. 新的異常系統(tǒng)主要圍繞 AFError
, 一個繼承了 Error
的枚舉類型, 包含四個主要的 case.
-
.invalidURL(url: URLConvertible)
- 創(chuàng)建URL
失敗的時候返回一個URLConvertible
類型的值 -
.parameterEncodingFailed(reason: ParameterEncodingFailureReason)
- 當(dāng)其中一個參數(shù)編碼出錯的時候就會拋出錯誤并返回 -
.multipartEncodingFailed(reason: MultipartEncodingFailureReason)
- multipart 編碼出錯就會拋出錯誤并返回 -
.responseValidationFailed(reason: ResponseValidationFailureReason)
- 當(dāng)調(diào)用validate()
拋出錯誤時捕獲然后拋出到外部. -
.responseSerializationFailed(reason: ResponseSerializationFailureReason)
- 返回的數(shù)據(jù)序列化出錯時會拋出異常并返回.
每一個 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("參數(shù)編碼失敗: \(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("請求返回狀態(tài)碼出錯: \(code)")
}
case .responseSerializationFailed(let reason):
print("請求返回內(nèi)容序列化失敗: \(error.localizedDescription)")
print("失敗理由: \(reason)")
}
print("錯誤: \(error.underlyingError)")
} else if let error = error as? URLError {
print("URL 錯誤: \(error)")
} else {
print("未知錯誤: \(error)")
}
}
新的設(shè)計給你的處理方式更多的自由, 可以在你需要的時候深入到最具體的 error. 這也會讓原本要四處應(yīng)對 NSError
的開發(fā)者更加輕松地完成工作. 在 Alamofire 里通過使用自定義的 Error
類型, 我們可以看到 Result
和 Response
的泛型參數(shù)縮減到了只有一個, 簡化了返回數(shù)據(jù)序列化的邏輯.
查看 PR-1419 獲取更多信息.
Parameter Encoding Protocol 參數(shù)編碼的協(xié)議
ParameterEncoding
枚舉類型在過去兩年很好地解決了問題. 但我們在 Alamofire 4 里想要定位的時候卻感覺到了一些局限.
-
.url
總讓人有點迷惑, 因為它是一個 HTTP 協(xié)議定義的地址 -
.urlEncodedInURL
跟.url
總是會混淆起來, 讓人分不清它們行為的區(qū)別 -
.JSON
和.PropertyList
編碼不能自定義編碼格式或者寫入的方式 -
.Custom
編碼對于用戶來說太難掌握
因為這些原因, 我們決定在 Alamofire 4 把這個枚舉去掉! 現(xiàn)在, ParameterEncoding
變成了一個協(xié)議, 加入了 Parameters
的類型別名去創(chuàng)建你的參數(shù)字典, 并且通過遵守這個協(xié)議建立了三個編碼結(jié)構(gòu)體 URLEncoding
, JSONEncoding
和 PropertyList
.
public typealias Parameters = [String: Any]
public protocol ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
URL Encoding (參數(shù)編碼)
新的 URLEncoding
結(jié)構(gòu)體包含了一個 Destination
枚舉, 支持三種類型的目標(biāo)編碼
-
.methodDependent
- 對于GET
,HEAD
和DELETE
方法使用 query 字符串, 而別的 HTTP 方法則會編碼為 HTTP body. -
.queryString
- 設(shè)置或者往現(xiàn)有的 queryString 里增加內(nèi)容 -
.httpBody
- 設(shè)置請求的 HTTP body 內(nèi)容
這些目標(biāo)編碼格式會讓你更容易控制 URLRequest
的參數(shù)編碼方式. 創(chuàng)建請求依舊使用和之前一樣的方式, 不管編碼的形式怎樣, 都會保持與之前一樣的默認(rèn)行為.
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)
// 便利的靜態(tài)屬性 (我們想鼓勵大家使用這種更簡潔的形式)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.default)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.queryString)
Alamofire.request(urlString, parameters: parameters, encoding: URLEncoding.httpBody)
JSON Encoding (JSON 編碼)
新的 JSONEncoding
結(jié)構(gòu)體開放了讓你自定義 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)
// 便利的靜態(tài)屬性 (我們想鼓勵大家使用這種更簡潔的形式)
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.default)
Alamofire.request(urlString, parameters: parameters, encoding: JSONEncoding.prettyPrinted)
Property List Encoding (屬性列表編碼)
新的 PropertyListEncoding
結(jié)構(gòu)體允許自定義 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)
// 便利的靜態(tài)屬性 (我們想鼓勵大家使用這種更簡潔的形式)
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.xml)
Alamofire.request(urlString, parameters: parameters, encoding: PropertyListEncoding.binary)
Custom Encoding 自定義編碼
建立一個自定義的 ParameterEncoding
只要遵守這個協(xié)議建立類型即可. 想要獲取更多相關(guān)例子, 請查看下面的 README
查看 PR-1465 獲取更多信息
Request Subclasses (Request 的子類)
在 Alamofire 4, request
, download
, upload
和 stream
的 API 不會再返回 Request
, 他們會返回特定的 Request
子類. 有下面幾個引導(dǎo)我們做出這個改變的現(xiàn)實原因和社區(qū)的疑問:
-
Progress:
progress
方法的行為會在 upload 請求里會很容易讓人迷惑.-
progress
在一個 upload 請求里返回的是什么? 上傳的進(jìn)度? 還是返回內(nèi)容的下載進(jìn)度? - 如果都返回, 那我們怎么區(qū)分他們, 在什么時候能知道是到底返回的是哪一個?
-
-
Response Serializers: 返回內(nèi)容的序列化是為了 data 和 upload 請求設(shè)計的, donwload 和 stream 請求并不需要序列化.
- 你要怎么才能在下載完成時獲取到文件的地址?
-
responseData
,responseString
和responseJSON
對于一個 donwload 請求來說意味著什么? stream 請求呢?
Alamofire 4 現(xiàn)在有四個 Request
的子類, 并且每個字類都有一些特有的 API. 這樣就可以讓每一個子類能夠通過建立 extension 來定制特定類型的請求.
open class Request {
// 包含了共有的屬性, 驗證, 和狀態(tài)方法
// 遵守 CustomStringConvertible 和 CustomDebugStringConvertible
}
open class DataRequest: Request {
// 包含了數(shù)據(jù)流(不要跟 StreamRequest 混淆)和下載進(jìn)度的方法
}
open class DownloadRequest: Request {
// 包含了下載位置和選項, 已下載的數(shù)據(jù)以及進(jìn)度方法
}
open class UploadRequest: DataRequest {
// 繼承了所有 DataRequest 的方法, 并且包含了上傳進(jìn)度的方法
}
open class StreamRequest: Request {
// 只繼承了 Request, 目前暫時沒有任何自定義的 API
}
通過這樣的切分, Alamofire 現(xiàn)在可以為每一個類型的請求自定義相關(guān)的 API. 這會覆蓋到所有可能的需求, 但讓我們花點時間來仔細(xì)了解一下這會如何改變進(jìn)度匯報和下載地址.
查看 PR-1455 獲取更多信息
Download and Upload Progress (下載和上傳你進(jìn)度)
Data, download 和 upload 請求的進(jìn)度匯報系統(tǒng)完全重新設(shè)計了一遍. 每一個請求類型都包含有一個閉包, 每當(dāng)進(jìn)度更新的時候, 就會調(diào)用閉包并且傳入 Progress
類型的參數(shù). 這個閉包會在指定的隊列被調(diào)用, 默認(rèn)為主隊列.
Data Request 進(jìn)度
Alamofire.request(urlString)
.downloadProgress { progress in
// 默認(rèn)在主隊列調(diào)用
print("下載進(jìn)度: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}
Download Request 進(jìn)度
Alamofire.download(urlString, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
// 在 .utility 隊列里調(diào)用
print("下載進(jìn)度: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}
Upload Request 進(jìn)度
Alamofire.upload(data, to: urlString, withMethod: .post)
.uploadProgress { progress in
// 默認(rèn)在主隊列調(diào)用
print("上傳進(jìn)度: \(progress.fractionCompleted)")
}
.downloadProgress { progress in
// 默認(rèn)在主隊列調(diào)用
print("下載進(jìn)度: \(progress.fractionCompleted)")
}
.responseData { response in
debugPrint(response)
}
現(xiàn)在很容易就可以區(qū)分開 upload request 里的上傳和下載進(jìn)度.
查看 PR-1455 獲取更多信息.
Download File Destinations 文件下載地址
在 Alamofire 3.x, 順利完成的 download requests 總是會在 destination
回調(diào)里把臨時文件移動到最終目標(biāo)文件夾里. 這很方便, 但也同時帶來了幾個限制:
-
Forced
- API 強(qiáng)制你去提供一個 destination 閉包來移動文件, 即使你驗證過后不想移動文件了. -
Limiting
- 沒有任何方式可以去調(diào)整文件系統(tǒng)移動文件的優(yōu)先級別.- 如果你需要在移動到目標(biāo)文件夾之前刪掉之前存在的文件呢?
- 如果你需要在移動臨時文件之前創(chuàng)建目錄呢?
這些限制都會在 Alamofire 4 里都不復(fù)存在. 首先是 optional 的 destination 閉包. 現(xiàn)在, destination
默認(rèn)為 nil, 意味著文件系統(tǒng)不會移動文件, 并且會返回臨時文件的 URL.
Alamofire.download(urlString).responseData { response in
print("臨時文件的 URL: \(response.temporaryURL)")
}
我們將會恢復(fù)
DownloadResponse
類型, 更多詳細(xì)信息請查看 Reponse Serializers 章節(jié).
Download Options 下載選項
另外一個主要的改變是 destination 閉包里面加上了下載選項, 讓你可以進(jìn)行更多文件系統(tǒng)操作. 為了達(dá)到目的, 我們建立了一個 DownloadOptions
類型并且添加到 DownloadFileDestination
閉包里.
public typealias DownloadFileDestination = (
_ temporaryURL: URL,
_ response: HTTPURLResponse)
-> (destinationURL: URL, options: DownloadOptions)
現(xiàn)階段支持的兩個 DownloadOptions
是:
-
.createIntermediateDirectories
- 如果有指定的下載地址的話, 會為下載地址創(chuàng)建相應(yīng)的目錄 -
.removePreviousFile
- 如果有指定的下載地址的話, 會自動替代掉同名文件
這兩個選項可以像下面這樣用:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, to: destination).response { response in
debugPrint(response)
}
如果一個異常在文件系統(tǒng)操作時拋出的話, DownloadResponse
的 error
就會是 URLError
類型.
查看 PR-1462 獲取更多信息.
Response Validation 數(shù)據(jù)驗證
在 Alamofire 4 里有幾個可以加強(qiáng)數(shù)據(jù)驗證系統(tǒng)的地方. 包括了:
-
Validation
回調(diào)閉包里傳入的data
-
Request
子類可以自定義數(shù)據(jù)驗證系統(tǒng), 例如 download 請求里的temporaryURL
和destinationURL
暴露到了回調(diào)閉包里
通過繼承 Request
, 每一個 Request
的子類都可以自定義一套數(shù)據(jù)驗證的閉包(typealias)和請求的 API.
Data Request 數(shù)據(jù)請求
DataRequest
(UploadRequest
的父類)暴露出來的 Validation
目前是這樣定義的:
extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
}
直接在閉包里把 Data?
暴露出來, 你就不需要再給 Request
增加一個 extension 去訪問這個屬性了. 現(xiàn)在你可以直接這樣子做:
Alamofire.request(urlString)
.validate { request, response, data in
guard let data = data else { return .failure(customError) }
// 1) 驗證返回的數(shù)據(jù)保證接下來的操作不會出錯
// 2) 如果驗證失敗, 你可以把錯誤信息返回出去, 甚至加上自定義的 error
return .success
}
.response { response in
debugPrint(response)
}
Download Request 下載請求
DownloadRequest
里的 Validation
閉包跟 DataRequest
里的很像, 但為了下載任務(wù)做了更多的定制.
extension DownloadRequest {
public typealias Validation = (
_ request: URLRequest?,
_ response: HTTPURLResponse,
_ temporaryURL: URL?,
_ destinationURL: URL?)
-> ValidationResult
}
temporaryURL
和 destinationURL
參數(shù)現(xiàn)在讓你可以在閉包內(nèi)直接獲取到服務(wù)器返回的數(shù)據(jù). 這可以讓你校驗下載好的文件, 在有需要的時候可以拋出一個自定義的錯誤.
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)
}
通過直接在閉包里暴露服務(wù)器返回的數(shù)據(jù), 這里面的所有異常都可以在 Validation
閉包里捕獲到, 并且可以自定義錯誤信息. 如果這里獲取到的信息和 response 序列化回調(diào)里一樣的話, response 可以用來處理錯誤信息而不是簡單地把邏輯賦值過來. 具體的例子, 請查看下面的 README.
查看 PR-1461 獲取更多信息.
Response Serializers 返回數(shù)據(jù)序列化
Alamofire 3.x 里的序列化系統(tǒng)有這么幾個限制:
- 序列化的 API 可以用在 download 和 stream 請求里, 但卻會導(dǎo)致未知的行為發(fā)生
- 怎么在下載成功時獲取到文件 URL?
-
responseData
,responseString
或者responseJSON
會在 donwload 請求里產(chǎn)生怎樣的行為? stream 請求呢?
-
response
API 返回四個參數(shù)而不是封裝到一個Response
類型里.- 最大的問題是 API 任何改變都會導(dǎo)致前面行為的變化.
- 在序列化和反序列化的 API 之間切換會讓人迷惑, 同時導(dǎo)致難以 debug 的編譯錯誤.
就像你看到的, Alamofire 3.x 的這一套序列化系統(tǒng)有這么多限制. 所以, 在 Alamofire 4里, Request
類型首先被切分到各個子類里, 這么做給自定義序列化方式, 和自定義 API 留下了空間. 在我們更深入了解序列化方式之前, 我們先了解一下新的 Response
類型
Default Data Response
DefaultDataResponse
代表了未被序列化的服務(wù)器返回數(shù)據(jù). Alamofire 沒有做任何處理過的, 只是純粹地從 SessionDelegate
里獲取信息并且包裝在一個結(jié)構(gòu)體里面返回.
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)
}
Data Response
泛型 DataResponse
類型跟 Alamofire 3.x 里的 Response
一樣, 但內(nèi)部重構(gòu)并且包含了新的 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)
}
Default Download Response 默認(rèn)下載請求的 Response 類型
因為 donwload 請求跟 data 和 upload 請求很不一樣, 所以 Alamofire 4 包含了自定義的 donwload Response
類型. DefaultDownloadResponse
類型代表未序列化的返回數(shù)據(jù), 包含了所有 SessionDelegate
信息的結(jié)構(gòu)體.
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)
}
Download Response
新的泛型 DownloadResponse
跟 DataResponse
很像, 但包含了 download 請求特有的信息. DownloadResponse
類型在使用 DownloadRequest
時就會被返回. 這些新的 API 同樣也適用于 DataRequest
, 一樣能夠獲取臨時目錄的 url 和目標(biāo)目錄的 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 讓文件下載和序列化更加容易完成.
Custom Response Serializers 自定義序列化
如果你已經(jīng)創(chuàng)建了自定義的序列化, 你也許會想要拓展支持 data 和 download 請求, 就像我們在 Alamofire 序列化 API 里面做的一樣.. 如果你決定這么做, 可以仔細(xì)看一下 Alamofire 怎么在幾種 Request
類型里共享序列化方法, 然后把實現(xiàn)寫到 Request
里就可以了. 這可以讓我們 DRY up 邏輯并且避免重復(fù)的代碼.(Don't repeat yourself)
查看 PR-1457 獲取更多信息.