对于使用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"