對於使用Objective-C的開發者,必定很是熟悉AFNetworking
這個網絡框架。在蘋果推出的Swift以後,AFNetworking
的做者專門用Swift來編寫一個相似AFNetworking
的網絡框架,稱爲Alamofire
。Alamofire地址 >>ios
我分兩篇文章介紹如何使用Alamofire框架。文章的內容主要是翻譯Alamofire的readme。第二篇文章 >>git
爲了讓Alamofire
專一於核心網絡的實現,Alamofire生態系統還有另外兩個庫:github
UIImage
和UIImageView
的擴展、自定義圖像濾鏡、內存中自動清除和基於優先級的圖像下載系統。source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target '項目名稱' do pod 'Alamofire', '~> 4.7' end
iOS版本和Alamofire
版本能夠本身根據實際狀況自行更改。CocoaPods是比較經常使用的第三方庫管理工具,其餘方法就不詳細說了。json
Alamofire.request("http://baidu.com/")
直接在請求後面用點語法連接響應處理:swift
Alamofire.request("https://httpbin.org/get").responseJSON { response in print(response.request) // 原始的URL請求 print(response.response) // HTTP URL響應 print(response.data) // 服務器返回的數據 print(response.result) // 響應序列化結果,在這個閉包裏,存儲的是JSON數據 if let JSON = response.result.value { print("JSON: \(JSON)") } }
在上面的例子中,responseJSON
handler直接拼接到請求後面,當請求完成後被調用。這個閉包一旦收到響應後,就會處理這個響應,並不會由於等待服務器的響應而形成阻塞執行。請求的結果僅在響應閉包的範圍內可用。其餘任何與服務器返回的響應或者數據相關的操做,都必須在這個閉包內執行。api
Alamofire默認狀況下包含五種不一樣的響應handler:數組
// 響應 Handler - 未序列化的響應 func response( queue: DispatchQueue?, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self // 響應數據 Handler - 序列化成數據類型 func responseData( queue: DispatchQueue?, completionHandler: @escaping (DataResponse<Data>) -> Void) -> Self // 響應字符串 Handler - 序列化成字符串類型 func responseString( queue: DispatchQueue?, encoding: String.Encoding?, completionHandler: @escaping (DataResponse<String>) -> Void) -> Self // 響應 JSON Handler - 序列化成Any類型 func responseJSON( queue: DispatchQueue?, completionHandler: @escaping (DataResponse<Any>) -> Void) -> Self // 響應 PropertyList (plist) Handler - 序列化成Any類型 func responsePropertyList( queue: DispatchQueue?, completionHandler: @escaping (DataResponse<Any>) -> Void)) -> Self
全部的響應handler都不會對響應進行驗證。也就是說響應狀態碼在400..<500
和500..<600
範圍內,都不會觸發錯誤。緩存
response
handler不處理任何響應數據。它僅僅是從URL session delegate中轉發信息。ruby
Alamofire.request("https://httpbin.org/get").response { response in print("Request: \(response.request)") print("Response: \(response.response)") print("Error: \(response.error)") if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) { print("Data: \(utf8Text)") } }
通常狀況下不建議使用這種沒有響應序列化器的handler,而應該使用下面有特定序列化器的handler。bash
responseData
handler使用responseDataSerializer
(這個對象把服務器的數據序列化成其餘類型)來提取服務器返回的數據。若是沒有返回錯誤而且有數據返回,那麼響應Result
將會是.success
,value
是Data
類型。
Alamofire.request("https://httpbin.org/get").responseData { response in debugPrint("All Response Info: \(response)") if let data = response.result.value, let utf8Text = String(data: data, encoding: .utf8) { print("Data: \(utf8Text)") } }
responseString
handler使用responseStringSerializer
對象根據指定的編碼格式把服務器返回的數據轉換成String
。若是沒有返回錯誤而且服務器的數據成功地轉換爲String
,那麼響應Result
將會是.success
,value
是String
類型。
Alamofire.request("https://httpbin.org/get").responseString { response in print("Success: \(response.result.isSuccess)") print("Response String: \(response.result.value)") }
若是沒有指定編碼格式,將會使用服務器的HTTPURLResponse
指定的格式。若是服務器沒法肯定編碼格式,那麼默認使用.isoLatin1
。
responseJSON
handler使用responseJSONSerializer
根據指定的JSONSerialization.ReadingOptions
把服務器返回的數據轉換成Any
類型。若是沒有返回錯誤而且服務器的數據成功地轉換爲JSON
對象,那麼響應Result
將會是.success
,value
是Any
類型。
Alamofire.request("https://httpbin.org/get").responseJSON { response in debugPrint(response) if let json = response.result.value { print("JSON: \(json)") } }
全部JSON的序列化,都是使用JSONSerialization
完成的。
響應handler能夠連接在一塊兒:
Alamofire.request("https://httpbin.org/get") .responseString { response in print("Response String: \(response.result.value)") } .responseJSON { response in print("Response JSON: \(response.result.value)") }
注意:在同一個請求中使用多個響應handler,要求服務器的數據會被序列化屢次,每次對應一個handler。
默認狀況下,響應handler是在主隊列執行的。可是咱們也能夠自定義隊列:
let utilityQueue = DispatchQueue.global(qos: .utility) Alamofire.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in print("Executing response handler on utility queue") }
默認狀況下,Alamofire把全部完成的請求當作是成功的請求,不管響應的內容是什麼。若是響應有一個不能被接受的狀態碼或者MIME類型,在響應handler以前調用validate
將會產生錯誤。
Alamofire.request("https://httpbin.org/get") .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) .responseData { response in switch response.result { case .success: print("Validation Successful") case .failure(let error): print(error) } }
自動驗證在200…299
範圍內的狀態碼;若是請求頭中有指定Accept
,那麼也會驗證響應頭的與請求頭Accept
同樣的Content-Type
。
Alamofire.request("https://httpbin.org/get").validate().responseJSON { response in switch response.result { case .success: print("Validation Successful") case .failure(let error): print(error) } }
響應緩存是使用系統的框架URLCache
來處理的。它提供了內存和磁盤上的緩存,並容許咱們控制內存和磁盤的大小。
默認狀況下,Alamofire
利用共享的URLCache
。
HTTPMethod
列舉了下面的這些方法:
public enum HTTPMethod: String { case options = "OPTIONS" case get = "GET" case head = "HEAD" case post = "POST" case put = "PUT" case patch = "PATCH" case delete = "DELETE" case trace = "TRACE" case connect = "CONNECT" }
在使用Alamofire.request
時,能夠傳入方法參數:
Alamofire.request("https://httpbin.org/get") // 默認是get請求 Alamofire.request("https://httpbin.org/post", method: .post) Alamofire.request("https://httpbin.org/put", method: .put) Alamofire.request("https://httpbin.org/delete", method: .delete)
Alamofire支持三種參數編碼:URL
、JSON
和PropertyList
。還支持遵循了ParameterEncoding
協議的自定義編碼。
URLEncoding
類型建立了一個URL編碼的查詢字符串來設置或者添加到一個現有的URL查詢字符串,或者設置URL請求的請求體。查詢字符串是否被設置或者添加到現有的URL查詢字符串,或者被做爲HTTP請求體,決定於編碼的Destination
。編碼的Destination
有三個case:
.methodDependent
:爲GET
、HEAD
和DELETE
請求使用編碼查詢字符串來設置或者添加到現有查詢字符串,而且使用其餘HTTP方法來設置請求體。.queryString
:設置或者添加編碼查詢字符串到現有查詢字符串.httpBody
:把編碼查詢字符串做爲URL請求的請求體一個編碼請求的請求體的Content-Type
字段被設置爲application/x-www-form-urlencoded; charset=utf-8
。由於沒有公開的標準說明如何編碼集合類型,因此按照慣例在key後面添加[]
來表示數組的值(foo[]=1&foo[]=2
),在key外面包一箇中括號來表示字典的值(foo[bar]=baz
)。
let parameters: Parameters = ["foo": "bar"] // 下面這三種寫法是等價的 Alamofire.request("https://httpbin.org/get", parameters: parameters) // encoding 默認是`URLEncoding.default` Alamofire.request("https://httpbin.org/get", parameters: parameters, encoding: URLEncoding.default) Alamofire.request("https://httpbin.org/get", parameters: parameters, encoding: URLEncoding(destination: .methodDependent)) // https://httpbin.org/get?foo=bar
let parameters: Parameters = [ "foo": "bar", "baz": ["a", 1], "qux": [ "x": 1, "y": 2, "z": 3 ] ] // 下面這三種寫法是等價的 Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters) Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: URLEncoding.default) Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: URLEncoding.httpBody) // HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3
Bool
類型參數的編碼URLEncoding.BoolEncoding
提供了兩種編碼方式:
.numeric
:把true
編碼爲1
,false
編碼爲0
.literal
:把true
編碼爲true
,false
編碼爲false
默認狀況下:Alamofire使用.numeric
。
可使用下面的初始化函數來建立URLEncoding
,指定Bool編碼的類型:
let encoding = URLEncoding(boolEncoding: .literal)
Array
類型參數編碼URLEncoding.ArrayEncoding
提供了兩種編碼方式:
.brackets
: 在每一個元素值的key後面加上一個[]
,如foo=[1,2]
編碼成foo[]=1&foo[]=2
.noBrackets
:不添加[]
,例如foo=[1,2]
編碼成``foo=1&foo=2`默認狀況下,Alamofire使用.brackets
。
可使用下面的初始化函數來建立URLEncoding
,指定Array編碼的類型:
let encoding = URLEncoding(arrayEncoding: .noBrackets)
JSONEncoding
類型建立了一個JOSN對象,並做爲請求體。編碼請求的請求頭的Content-Type
請求字段被設置爲application/json
。
let parameters: Parameters = [ "foo": [1,2,3], "bar": [ "baz": "qux" ] ] // 下面這兩種寫法是等價的 Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default) Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding(options: [])) // HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}
PropertyListEncoding
根據關聯格式和寫選項值,使用PropertyListSerialization
來建立一個屬性列表對象,並做爲請求體。編碼請求的請求頭的Content-Type
請求字段被設置爲application/x-plist
。
若是提供的ParameterEncoding
類型不能知足咱們的要求,能夠建立自定義編碼。下面演示如何快速自定義一個JSONStringArrayEncoding
類型把JSON字符串數組編碼到請求中。
struct JSONStringArrayEncoding: ParameterEncoding { private let array: [String] init(array: [String]) { self.array = array } func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { var urlRequest = urlRequest.urlRequest let data = try JSONSerialization.data(withJSONObject: array, options: []) if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") } urlRequest.httpBody = data return urlRequest } }
ParameterEncoding
API能夠在建立網絡請求外面使用。
let url = URL(string: "https://httpbin.org/get")! var urlRequest = URLRequest(url: url) let parameters: Parameters = ["foo": "bar"] let encodedURLRequest = try URLEncoding.queryString.encode(urlRequest, with: parameters)
能夠直接在請求方法添加自定義HTTP請求頭,這有利於咱們在請求中添加請求頭。
let headers: HTTPHeaders = [ "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", "Accept": "application/json" ] Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in debugPrint(response) }
對於那些不變的請求頭,建議在URLSessionConfiguration
設置,這樣就能夠自動被用於任何URLSession
建立的URLSessionTask
。
默認的Alamofire SessionManager
爲每個請求提供了一個默認的請求頭集合,包括:
Accept-Encoding
,默認是gzip;q=1.0, compress;q=0.5
。Accept-Language
,默認是系統的前6個偏好語言,格式相似於en;q=1.0
。User-Agent
,包含當前應用程序的版本信息。例如iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0
。若是要自定義這些請求頭集合,咱們必須建立一個自定義的URLSessionConfiguration
,defaultHTTPHeaders
屬性將會被更新,而且自定義的會話配置也會應用到新的SessionManager
實例。
認證是使用系統框架URLCredential
和URLAuthenticationChallenge
實現的。
在合適的時候,在一個請求的authenticate
方法會自動提供一個URLCredential
給URLAuthenticationChallenge
:
let user = "user" let password = "password" Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)") .authenticate(user: user, password: password) .responseJSON { response in debugPrint(response) }
根據服務器實現,Authorization
header也多是適合的:
let user = "user" let password = "password" var headers: HTTPHeaders = [:] if let authorizationHeader = Request.authorizationHeader(user: user, password: password) { headers[authorizationHeader.key] = authorizationHeader.value } Alamofire.request("https://httpbin.org/basic-auth/user/password", headers: headers) .responseJSON { response in debugPrint(response) }
let user = "user" let password = "password" let credential = URLCredential(user: user, password: password, persistence: .forSession) Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)") .authenticate(usingCredential: credential) .responseJSON { response in debugPrint(response) }
注意:使用URLCredential
來作認證,若是服務器發出一個challenge,底層的URLSession
實際上最終會發兩次請求。第一次請求不會包含credential,而且可能會觸發服務器發出一個challenge。這個challenge會被Alamofire接收,credential會被添加,而後URLSessin
會重試請求。
Alamofire能夠把服務器的數據下載到內存(in-memory)或者硬盤(on-disk)中。全部Alamofire.request
API下載的數據都是存儲在內存中。這比較適合小文件,更高效;可是不適合大文件,由於大文件會把內存耗盡。咱們要使用Alamofire.download
API把服務器的數據下載到硬盤中。
下面這個方法只適用於macOS
。由於在其餘平臺不容許在應用沙盒外訪問文件系統。下面會講到如何在其餘平臺下載文件。
Alamofire.download("https://httpbin.org/image/png").responseData { response in if let data = response.result.value { let image = UIImage(data: data) } }
咱們能夠提供一個DownloadFileDestination
閉包把臨時文件夾的文件移動到一個目標文件夾。在臨時文件真正移動到destinationURL
以前,閉包內部指定的DownloadOptions
將會被執行。目前支持的DownloadOptions
有下面兩個:
.createIntermediateDirectories
:若是指定了目標URL,將會建立中間目錄。.removePreviousFile
:若是指定了目標URL,將會移除以前的文件let destination: DownloadRequest.DownloadFileDestination = { _, _ in let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let fileURL = documentsURL.appendPathComponent("pig.png") return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) } Alamofire.download(urlString, to: destination).response { response in print(response) if response.error == nil, let imagePath = response.destinationURL?.path { let image = UIImage(contentsOfFile: imagePath) } }
也能夠直接使用建議的下載目標API:
let destination = DownloadRequest.suggestedDownloadDestination(directory: .documentDirectory) Alamofire.download("https://httpbin.org/image/png", to: destination)
全部的DownloadRequest
均可以使用downloadProgress
API來反饋下載進度。
Alamofire.download("https://httpbin.org/image/png") .downloadProgress { progress in print("Download Progress: \(progress.fractionCompleted)") } .responseData { response in if let data = response.result.value { let image = UIImage(data: data) } }
downloadProgress
API還能夠接受一個queue
參數來指定下載進度閉包在哪一個DispatchQueue
中執行。
let utilityQueue = DispatchQueue.global(qos: .utility) Alamofire.download("https://httpbin.org/image/png") .downloadProgress(queue: utilityQueue) { progress in print("Download Progress: \(progress.fractionCompleted)") } .responseData { response in if let data = response.result.value { let image = UIImage(data: data) } }
若是一個DownloadRequest
被取消或中斷,底層的URL會話會生成一個恢復數據。恢復數據能夠被從新利用並在中斷的位置繼續下載。恢復數據能夠經過下載響應訪問,而後在從新開始請求的時候被利用。
重要:在iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1中,resumeData
會被後臺URL會話配置破壞。由於在resumeData
的生成邏輯有一個底層的bug,不能恢復下載。具體狀況能夠到Stack Overflow看看。
class ImageRequestor { private var resumeData: Data? private var image: UIImage? func fetchImage(completion: (UIImage?) -> Void) { guard image == nil else { completion(image) ; return } let destination: DownloadRequest.DownloadFileDestination = { _, _ in let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let fileURL = documentsURL.appendPathComponent("pig.png") return (fileURL, [.removePreviousFile, .createIntermediateDirectories]) } let request: DownloadRequest if let resumeData = resumeData { request = Alamofire.download(resumingWith: resumeData) } else { request = Alamofire.download("https://httpbin.org/image/png") } request.responseData { response in switch response.result { case .success(let data): self.image = UIImage(data: data) case .failure: self.resumeData = response.resumeData } } } }
使用JOSN或者URL編碼參數上傳一些小數據到服務器,使用Alamofire.request
API就已經足夠了。若是須要發送很大的數據,須要使用Alamofire.upload
API。當咱們須要在後臺上傳數據時,也可使用Alamofire.upload
。
let imageData = UIPNGRepresentation(image)! Alamofire.upload(imageData, to: "https://httpbin.org/post").responseJSON { response in debugPrint(response) }
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov") Alamofire.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in debugPrint(response) }
Alamofire.upload( multipartFormData: { multipartFormData in multipartFormData.append(unicornImageURL, withName: "unicorn") multipartFormData.append(rainbowImageURL, withName: "rainbow") }, to: "https://httpbin.org/post", encodingCompletion: { encodingResult in switch encodingResult { case .success(let upload, _, _): upload.responseJSON { response in debugPrint(response) } case .failure(let encodingError): print(encodingError) } } )
全部的UploadRequest
均可以使用uploadProgress
和downloadProgress
APIs來反饋上傳和下載進度。
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov") Alamofire.upload(fileURL, to: "https://httpbin.org/post") .uploadProgress { progress in // 默認在主線程中執行 print("Upload Progress: \(progress.fractionCompleted)") } .downloadProgress { progress in // 默認在主線程中執行 print("Download Progress: \(progress.fractionCompleted)") } .responseJSON { response in debugPrint(response) }
Alamofire在一個請求週期內收集時間,並建立一個Tineline
對象,它是響應類型的一個屬性。
Alamofire.request("https://httpbin.org/get").responseJSON { response in print(response.timeline) }
上面的Timeline
信息包括:
在iOS和tvOS 10和macOS 10.12中,蘋果發佈了新的URLSessionTaskMetrics
APIs。這個任務指標封裝了關於請求和響應執行的神奇統計信息。這個API和Timeline
很是類似,可是提供了不少Alamofire沒有提供的統計信息。這些指標能夠經過任何響應去訪問。
Alamofire.request("https://httpbin.org/get").responseJSON { response in print(response.metrics) }
注意:這些API只能在iOS和tvOS 10和macOS 10.12中使用。因此,根據部署目標,可能須要加入版本判斷:
Alamofire.request("https://httpbin.org/get").responseJSON { response in if #available(iOS 10.0. *) { print(response.metrics) } }
調試平臺問題很讓人厭煩。慶幸的是,Alamofire的Request
對象遵循了CustomStringConvertible
和CustomDebugStringConvertible
協議來提供一些很是有用的調試工具。
let request = Alamofire.request("https://httpbin.org/ip") print(request) // GET https://httpbin.org/ip (200)
let request = Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]) debugPrint(request)
輸出:
$ curl -i \
-H "User-Agent: Alamofire/4.0.0" \ -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \ -H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \ "https://httpbin.org/get?foo=bar"