序:做过iOS开发的人肯定都知道AFN,现在Swift逐渐流行AFN团队又用Swift写了Alamofire。从头开始学习一下,看看新的Alamofire有什么性能的优化和不同。
大家都知道Alamofire是一个HTTP的网络封装库,首先我们肯定要先知道用Alamofire我们可以干什么。
功能特点
1、请求连接,处理接受不同类型的返回
2、 URL / JSON / plist 参数编码
3、上传 File / Data / Stream / MultipartFormData
4、用请求或者恢复数据下载文件
5、身份认证和url凭证
6、HTTP 返回验证
7、上传或者下载进程显示
8、cURL命令输出
9、动态适应和重试请求
10、TLS证书和公钥锁
11、网络是否可用判断
12、完整的单元检测
组件库
- AlamofireImage
- AlamofireNetworkActivityIndicator
环境要求
- iOS 8.0 / macOS 10.10 / tvOS 9.0 / watchOS 2.0
- Xcode 8.1
- Swift 3.0
安装
CocoaPods
podfile文件:
代码语言:javascript复制source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
pod 'Alamofire', '~> 4.4'
end
然后到终端进入项目目录:
代码语言:javascript复制$ pod install
其他安装方法见gitHub
应用
首先导入Alamofire模块,发一个简单的GET请求
代码语言:javascript复制import Alamofire
Alamofire.request("https://httpbin.org/get")
返回的回调:
代码语言:javascript复制Alamofire.request("https://httpbin.org/get").responseJSON { response in
print("Request: (String(describing: response.request))") // original url request
print("Response: (String(describing: response.response))") // http url response
print("Result: (response.result)") // response serialization result
if let json = response.result.value {
print("JSON: (json)") // serialized json response
}
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: (utf8Text)") // original server data as UTF8 string
}
}
Alamofire 包括默认回调总共有五种返回类型回调包括:
代码语言:javascript复制// Response Handler - Unserialized Response
func response(
queue: DispatchQueue?,
completionHandler: @escaping (DefaultDataResponse) -> Void)
-> Self
// Response Data Handler - Serialized into Data
func responseData(
queue: DispatchQueue?,
completionHandler: @escaping (DataResponse<Data>) -> Void)
-> Self
// Response String Handler - Serialized into String
func responseString(
queue: DispatchQueue?,
encoding: String.Encoding?,
completionHandler: @escaping (DataResponse<String>) -> Void)
-> Self
// Response JSON Handler - Serialized into Any
func responseJSON(
queue: DispatchQueue?,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
// Response PropertyList (plist) Handler - Serialized into Any
func responsePropertyList(
queue: DispatchQueue?,
completionHandler: @escaping (DataResponse<Any>) -> Void))
-> Self
响应验证
在默认情况下,Alamofire对待任何完整的请求不管什么响应内容都是成功的,加上验证之后,在得到响应回调之前,先要经过验证,类型或者状态不匹配的则会报错。
手动验证
代码语言:javascript复制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-300之间的区间,且内容类型是响应能接受的匹配类型。
代码语言:javascript复制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.
HTTP 请求方式
HTTP请求方式的枚举列表在下面的文件定义 RFC 7231 §4.3 :
代码语言:javascript复制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的method的参数设置
代码语言:javascript复制Alamofire.request("https://httpbin.org/get") // method defaults to `.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)
默认是Get方式
参数编码
Alamofire支持三种类型的参数编码包括:URL、JSON和PropertyList。它还可以支持任何自定义编码符合ParameterEncoding协议。
URL Encoding
GET请求的URL编码
代码语言:javascript复制let parameters: Parameters = ["foo": "bar"]
// All three of these calls are equivalent
Alamofire.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `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
POST请求的URL编码
代码语言:javascript复制let parameters: Parameters = [
"foo": "bar",
"baz": ["a", 1],
"qux": [
"x": 1,
"y": 2,
"z": 3
]
]
// All three of these calls are equivalent
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
JSON Encoding
代码语言:javascript复制let parameters: Parameters = [
"foo": [1,2,3],
"bar": [
"baz": "qux"
]
]
// Both calls are equivalent
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"}}
Property List Encoding
PropertyListEncoding使用PropertyListSerialization创建一个参数对象的plist表示,根据相关的格式和写作选项值,设置为请求的主体。content - type HTTP请求头字段的编码设置为 application/x-plist
.。
Custom Encoding
代码语言:javascript复制struct JSONStringArrayEncoding: ParameterEncoding {
private let array: [String]
init(array: [String]) {
self.array = array
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
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
}
}
给URLRequest 的参数手动编码
代码语言:javascript复制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 Headers(请求头)
给一个请求添加一个自定义的请求头,这个请求头必须是全局的,在你请求的时候更容易去获取和改变请求头。
代码语言:javascript复制let headers: HTTPHeaders = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
"Accept": "application/json"
]
Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
debugPrint(response)
}
身份认证
Authentication依赖于 URLCredential
和URLAuthenticationChallenge
.这两个系统框架。
Supported Authentication Schemes
- HTTP Basic
- HTTP Digest
- Kerberos
- NTLM
HTTP Basic Authentication
代码语言:javascript复制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)
}
根据服务器的实现方法,也可能需要一个授权头
代码语言:javascript复制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)
}
证书认证
代码语言:javascript复制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)
}
下载文件
代码语言:javascript复制Alamofire.download("https://httpbin.org/image/png").responseData { response in
if let data = response.result.value {
let image = UIImage(data: data)
}
}
下载文件目的地
代码语言:javascript复制let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("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.
代码语言:javascript复制let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download("https://httpbin.org/image/png", to: destination)
下载进度
代码语言:javascript复制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)
}
}
下载进度的API还可以设置线程参数,线程参数可以决定下载进度在哪个线程调起。
代码语言:javascript复制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)
}
}
恢复下载
代码语言:javascript复制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.appendingPathComponent("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
}
}
}
}
上传数据到服务器
上传Data类型:
代码语言:javascript复制let imageData = UIPNGRepresentation(image)!
Alamofire.upload(imageData, to: "https://httpbin.org/post").responseJSON { response in
debugPrint(response)
}
上传一个文件:
代码语言:javascript复制let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
Alamofire.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in
debugPrint(response)
}
上传 Multipart Form Data
代码语言:javascript复制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)
}
}
)
上传进度
代码语言:javascript复制let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
Alamofire.upload(fileURL, to: "https://httpbin.org/post")
.uploadProgress { progress in // main queue by default
print("Upload Progress: (progress.fractionCompleted)")
}
.downloadProgress { progress in // main queue by default
print("Download Progress: (progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}