Alamofire 用法(一)

Tuesday, April 10, 2018

前面有说到,之前一直用 Moya 来进行网络请求,所以对 Alamofire 的一些技术细节反倒是不太了解了,从篇文章开始,会逐步的记录下关于 Alamofire 的详细用法和一些源码解析。

组件库

除了 Alamofire 本身的核心网络实现,里面还有另外两个库:

  • AlamofireImag:一个图片库,包括图片响应序列化器,UIImage 和 UIImageView 的扩展,自定义图像滤镜,内存中自带清除和基于优先级的图像下载系统。
  • AlamofireNetworkActivityIndicator: 控制 iOS 应用的网络活动指示器。包括可配置的延迟计时器来帮助减少闪光和支持 URLSession 实例。

使用方法

发请求

<code class="language-objectivec">Alamofire.request("")
</code>

响应处理

<code class="language-objectivec">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)")
    }
}
</code>
  • Alamofire 默认情况下包含五种不同的响应 handler:

    // 响应 Handler - 未序列化的响应 func response( queue: DispatchQueue?, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self

    // 响应数据 Handler - 序列化成数据类型 func responseData( queue: DispatchQueue?, completionHandler: @escaping (DataResponse) -> Void) -> Self

    // 响应字符串 Handler - 序列化成字符串类型 func responseString( queue: DispatchQueue?, encoding: String.Encoding?, completionHandler: @escaping (DataResponse) -> Void) -> Self

    // 响应 JSON Handler - 序列化成Any类型 func responseJSON( queue: DispatchQueue?, completionHandler: @escaping (DataResponse) -> Void) -> Self

    // 响应 PropertyList (plist) Handler - 序列化成Any类型 func responsePropertyList( queue: DispatchQueue?, completionHandler: @escaping (DataResponse) -> Void)) -> Self

  • 这里要说一句的是,所有的响应 handle 都不会被响应进行验证,哪怕响应状态码在400-499,500-599。也不会触发错误。

  • 下面我们分别说说这几种 handle

响应 Handler

response handler 不处理任何数据,仅仅从 URL 会话中转发信息。

<code class="language-objectivec">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)")
    }
}
</code>
  • 一般情况下不建议使用这种没有响应序列化器的 handler。

响应数据 Handler

responseData 使用 responseDataSerializer(这个对象把服务器的数据序列化成其他类型)来提取服务器的返回的数据。如果没有返回错误并且有数据返回,那么响应 Result 将会是.success,value 是 Data 类型。

<code class="language-objectivec">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)")
    }
}
</code>

响应字符串 Handler

responseString handler 使用 responseStringSerializer 对象根据特定的编码格式把服务器返回的数据转换成 String。如果没有返回错误并且服务器的数据成功转换成 String,那么响应 Result 将会是.success,value 是 String 类型。

<code class="language-objectivec">Alamofire.request("https://httpbin.org/get").responseString { response in
    print("Success: \(response.result.isSuccess)")
    print("Response String: \(response.result.value)")
}
</code>
  • 如果没有指定编码格式,将会使用服务器的 HTTPURLResponse 指定的格式。如果服务器无法确定编码格式,那么默认使用.isoLatin1。

响应 JSON Handler

responseJSON handler 使用 responseJSONSerializer 对象根据指定的 JSONSerialization.ReadingOptions 把服务器返回的数据转换成 Any 类型。如果没有返回错误并且服务器的数据成功地转换为 JSON 对象,那么响应 Result 将会是 .success,value 是Any 类型。

<code class="language-objectivec">Alamofire.request("https://httpbin.org/get").responseJSON { response in
    debugPrint(response)

    if let json = response.result.value {
        print("JSON: \(json)")
    }
}
</code>
  • JSON 序列化是使用 JSONSerialization 完成的。

链式响应 Handler

<code class="language-objectivec">Alamofire.request("https://httpbin.org/get")
    .responseString { response in
        print("Response String: \(response.result.value)")
    }
    .responseJSON { response in
        print("Response JSON: \(response.result.value)")
    }
</code>
  • 在同一个请求中响应多个 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 之前调用验证将会产生错误。
手动验证
<code class="language-objectivec">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)
    }
}
</code>
自动验证
  • 自动验证在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 来处理的。它提供了内存和磁盘上的缓存,并允许我们控制内存和磁盘的大小。默认会利用共享的 URLCache。

HTTP 方法

  • 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 协议的自定义编码。

URL 编码

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)。

  • 使用 URL 编码参数的 GET 请求

    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

  • 使用 URL 编码参数的 POST 请求

    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

JSON 编码

JSONEncoding 类型创建了一个参数对象的 JSON 展示,并作为请求体。编码请求的请求头的 Content-Type 请求字段被设置为 application/json

  • 使用 JSON 编码参数的 POST 请求

    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”}}

  • 属性列表编码和自定义编码用的实在少,就不在这里阐述了。。。

  • 还可以手动 URL 请求参数编码

    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 请求头

可以在请求方法里添加自定义请求头

<code class="language-objectivec">let headers: HTTPHeaders = [
    "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    "Accept": "application/json"
]

Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
    debugPrint(response)
}
</code>
  • 对于那些不变的请求头,建议在 URLSessionConfiguration 设置,这样就可以自动被用于任何 URLSession 创建的 URLSessionTask。

  • 默认的 Alamofire SessionManager 为每一个请求提供了一个默认的请求头合集:

    • Accept-Encoding, 默认是gzip;q=1.0,compress,q=0.5
    • Accept-Language, 默认是系统的前6个偏好语言
    • User-Agent,包含当前应用程序的版本信息
    • 如果要自定义这些请求头集合,我们必须创建一个自定义的 URLSessionConfiguration,defautHTTPHeaders 属性将会被更新,并且自定义的会话配置也会应用到新的 SessionManager 实例。

认证

认证方案
  • HTTP Basic
  • HTTP Digest
  • Kerberos
  • NTLM
HTTP BASIC 认证

在合适的时候,在一个请求的 authenticate 方法会自动提供一个 URLCredential 给 URLAuthenticationChallenge

<code class="language-objectivec">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)
}
</code>

根据服务器实现,Authorization header 也是可能合适的:

<code class="language-objectivec">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)
}
</code>
使用 URLCredential 认证
<code class="language-objectivec">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)
}
</code>

将数据下载到文件

因为平时也很少会在 APP 中直接下载大型文件,所以这方面的知识,以后再补充了。。

上传数据到服务器

使用 JSON 数据或者 URL 编码参数上传一些小数据到服务器,使用 Alamofire.request API 就已经足够了。如果需要发送很大的数据,需要使用 Alamofire.upload API。当我们需要在后台上传数据时,也可以使用 Alamofire.upload。

上传数据
<code class="language-objectivec">let imageData = UIPNGRepresentation(image)!

Alamofire.upload(imageData, to: "https://httpbin.org/post").responseJSON { response in
    debugPrint(response)
}
</code>
上传文件
<code class="language-objectivec">let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")

Alamofire.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in
    debugPrint(response)
}
</code>

另外上传多部分表单数据和上传进度就暂时不阐述了,有兴趣的可以去 github 上面看看官方文档。

统计指标

时间表

Alamofire 在一个请求周期内手机时间,并创建一个 Timeline 对象,它是响应类型的一个属性。

<code class="language-objectivec">Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.timeline)
}
</code>
  • 包括如下信息

    • Latency: 延迟
    • Request Duration: 请求时间
    • Serializaiton Duration: 序列化时间
    • Total Duration: 总时间

URL 会话任务指标

在 iOS10.0 之后,苹果发布了 URLSessionTaskMetrics APIs,这个也是封装了统计信息,和 Timeline 有点像,但是会有更多的统计信息。

<code class="language-objectivec">Alamofire.request("https://httpbin.org/get").responseJSON { response in
    print(response.metrics)
}
</code>
iOSSwift

Alamofire 用法(二)

Moya 的实现(二)