本文的目的是单纯的翻译一下Alamofire的基本用法的文档,原文是在github上:https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md 你看到这篇文章的时候,可能官方文档会有更新(本文5.1.0版本)。个人也是按照自己的理解进行翻译,能力有限,翻译的不好的地方,希望看到的朋友们给我指正出来,感激不尽~
使用 Alamofire
介绍
Alamofire 提供了一个优雅的和可组合的接口去进行 HTTP 网络请求。它是在 Apple 官方的 Foundation 框架所提供的 URL Loading System 之上建立起来的,并不是自己去实现了这些HTTP的请求相关的功能。 URL Loading System 的核心是 URLSession 和 URLSessionTask 的子类们。Alamofire 把包括他们在内的许多 API 封装成了更加易于使用的API(interface)并且提供了开发 App(需要使用 HTTP 网络请求的) 所需要的各种网络方面的功能。但是,很重要的一点是你要知道 Alamofire 的核心功能是来自哪里的(URL Loading System ),因此,熟悉 Apple 的 URL Loading System是很重要的。归根结底,Alamofire 的网络功能是受限制于 URL Loading System 的能力的,因此遵守 URL Loading System 的行为和准则是必须的。
另外,在 Alamofire (和 URL Loading System 的一般情况)下的网络请求都是异步的。异步编程可能使不熟悉这个概念的程序员感到沮丧,但是异步编程的 好处 是非常多的。
旁注:AF命名空间和参考
以前版本的 Alamofire 的文档都使用了 Alamofire.request()
这样的示例来进行网络请求。这个 API 使用了Alamofire 的前缀,但是事实上没有这个前缀,也是可以的。在任何文件中 import Alamofire
都可以在文件中全局的使用 request
以及其他的方法(functions)。从 Alamofire 5 版本开始,这种使用方法被移除了,取而代之的是 AF
这个全局的对 Session.default
的引用(AF = Session.default)。这样做的好处是使得在每次使用 Alamofire 提供的便捷的功能的时候不会去污染 Alamofire
的全局命名空间,也不用每次使用 Alamofire 的时候都写一遍全局的 Session
API。同样的原因,Alamofire 拓展出来的类型们会使用 af
这种拓展名来把 Alamofire 提供的功能和其他拓展所提供的功能进行区分。
发一个网络请求(Making Requests)
Alamofire 提供了多个便捷的方法去发送网络请求。最简单的例子,只需要提供一个可以转换成(converted into)URL
的 String
就可以了 (第一个版本):
AF.request("https://httpbin.org/get").response { response in
debugPrint(response)
}
注:所有的例子都需要在文件中
import Alamofire
这个方法实际上是 Alamofire 的 Session
发送网络请求的两个顶层(top-level) API 中的一个,他的完整定义是这样的:
open func request<Parameters: Encodable>(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil) -> DataRequest
这个方法创建了一个 DataRequest
。在创建每个请求(request)的时候允许他们有自己不同的组成元素(components),他们可以有不同的 method
或者 headers
。 还允许每个请求都可以有自己的拦截器(RequestInterceptor
)和 参数编码(Encodable)。
除上面之外还有一些额外的方法可以让你使用
Parameters
类型的字典做为参数ParameterEncoding
作为参数类型进行网络请求。不建议再这些 API 了,它们会在将来被 Alamofire 移除掉。
这个 API 的 第二个版本 要简单的多:
open func request(_ urlRequest: URLRequestConvertible,
interceptor: RequestInterceptor? = nil) -> DataRequest
这个方法可以为任何一个符合 Alamofire 的 URLRequestConvertible
协议的类型创建一个 DataRequest
。所有之前版本的参数(对比上一个完整的函数中的参数)都被封装进了这个 URLRequestConvertible
的参数里面,这样就产生了一个非常强大的抽象。这一点我们在 高级用法 的文档中进行了讨论。
HTTP 请求方式 (HTTP Methods)
HTTPMethod
这个类型列举出了 RFC 7231 §4.3 中定义的 HTTP 请求方式们(HTTP Methods):
public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
public static let connect = HTTPMethod(rawValue: "CONNECT")
public static let delete = HTTPMethod(rawValue: "DELETE")
public static let get = HTTPMethod(rawValue: "GET")
public static let head = HTTPMethod(rawValue: "HEAD")
public static let options = HTTPMethod(rawValue: "OPTIONS")
public static let patch = HTTPMethod(rawValue: "PATCH")
public static let post = HTTPMethod(rawValue: "POST")
public static let put = HTTPMethod(rawValue: "PUT")
public static let trace = HTTPMethod(rawValue: "TRACE")
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
它里面的这些值可以被作为方法参数传递给 AF.request
API 使用:
AF.request("https://httpbin.org/get")
AF.request("https://httpbin.org/post", method: .post)
AF.request("https://httpbin.org/put", method: .put)
AF.request("https://httpbin.org/delete", method: .delete)
需要记住的很重要的一点是,不同的网络请求方式(HTTP methods)会有不同的语义,还会根据服务器的不同要求进行相应的参数编码。比如,URLSession
和 Alamofire
都不支持往 GET
的请求体里插入数据(passing body data)(那样 是 POST 的做法),那样做的话会报错。
Alamofire 还提供了一个 URLRequest
的拓展。里面的 httpMethod
属性起到了桥接的作用,这个属性把 URLRequest
中的 String
类型的 HTTP 请求方式(HTTP Methods) 转变为 Alamofire的 HTTPMethod
类型:
public extension URLRequest {
/// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
var method: HTTPMethod? {
get { return httpMethod.flatMap(HTTPMethod.init) }
set { httpMethod = newValue?.rawValue }
}
}
如果你想使用一个 Alamofire 的 HTTPMethod
不支持的请求方式,你可以拓展 HTTPMethod
来添加你自己的请求方式:
extension HTTPMethod {
static let custom = HTTPMethod(rawValue: "CUSTOM")
}
设置其他的 URLRequest
的属性(Setting Other URLRequest
Properties)
Alamofire 创建网络请求的时候提供了最通用的自定义请求参数,但是,有时候这些默认的参数并不能满足需求。通过传入各种参数的方式创建请求的时候,可以使用 RequestModifier
闭包来修改这个正在创建的请求。举个例子,可以设置这个请求的超时时间(URLRequest
'S timeoutInterval
)为5s,我们利用这个闭包(closure)来这样修改:
AF.request("https://httpbin.org/get", requestModifier: { $0.timeoutInterval = 5 }).response(...)
RequestModifier
也可以使用尾随闭包的语法形式:
AF.request("https://httpbin.org/get") { urlRequest in
urlRequest.timeoutInterval = 5
urlRequest.allowsConstrainedNetworkAccess = false
}
.response(...)
RequestModifier
仅仅适用于通过 URL
和自定义参数创建请求的时候(译者:上面第一个版本的请求),并不适用于直接从 URLRequestConvertible
里面的值们来提供参数值的那种创建请求的方式(译者:上面第二个版本的请求),因为第二个版本封装起来的那些值应该提供所有的请求所需的参数值。另外,一旦但多数的网络请求需要在创建的时候进行修改的话,那么还是推荐使用 URLRequestConvertible
(译者:第二种高级抽象出来的类型提供所有参数,在创建的时候就可以修改,第一种采用闭包,是创建完了之后对请求进行的配置)。你可以更多的在这里进行了解: Advanced Usage documentation。
请求参数和参数编码(Request Parameters and Parameter Encoders)
Alamofire 支持传递任何 Encodable
类型的数据作为网络请求的参数。这些参数会被一个符合 ParmeterEncoder
协议(protocol)的类型处理之后进行传递,添加到网络请求之中,然后通过网络发送出去。Alamofier 里面有两个符合 ParameterEncoder
的类型,他们分别是:JSONParameterEncoder
和 URLEncodedFormParameterEncoder
。这俩类型涵盖了几乎所有的现代网络请求服务所需的参数编码方式(XML的编码方式就作为练习交给使用我们的老铁们了)。
struct Login: Encodable {
let email: String
let password: String
}
let login = Login(email: "test@test.test", password: "testPassword")
AF.request("https://httpbin.org/post",
method: .post,
parameters: login,
encoder: JSONParameterEncoder.default).response { response in
debugPrint(response)
}
第一种参数编码方式(URLEncodedFormParameterEncoder)
URLEncodedFormParameterEncoder
类型的编码器将值编码成url编码(url-encoded)用以设置或者添加到现有的URL查询字符串中(译者:GET),或者设置为HTTP请求的请求体(HTTP body)(译者:POST)。通过设置编码器的 destination
来控制被编码后字符串设置到哪里(译者:URL中或者HTTP body中)。URLEncodedFormParameterEncoder.Destination
这个枚举有以下几个枚举值:
-
.methodDependent
- 把编码过后的结果设置到现有的.get
,.head
,.delete
类型的请求的URL查询字符串中,以及其他的HTTP请求类型的请求体之中(HTTP body)。 -
.queryString
- 把编码过后的字符串设置或者添加到网络请求的这个URL查询字符串中。 -
.httpBody
- 把编码过后的字符串设置到 HTTP 请求体中去(HTTP body)。
译者:第一种是根据情况自动选择,第二种是与 GET 相同类别的请求,第三种是与 POST 相同类型的请求
对于使用HTTP请求体(HTTP body)的网络请求,如果HTTP头(HTTP header)中的 Content-Type
没有被设置过,那么就会被设置成 application/x-www-form-urlencoded; charset=utf-8
在 Alamofire 内部的实现中,URLEncodedFormParameterEncoder
使用了 URLEncodedFormEncoder
去真正的执行把一个 Encodable
类型的数据编码成URL编码的字符串。这个编码器可以用于各种类型的自定义的类型们,包括有 Array
使用 ArrayEncoding
,Bool
使用 BoolEncoding
,Data
使用 DataEncoding
,Date
使用 DateEncoding
,编码键(keys)使用 KeyEncoding
, 空格(spaces)使用 SpaceEncoding
。
URL编码参数的GET请求(GET Request With URL-Encoded Parameters):
let parameters = ["foo": "bar"]
// All three of these calls are equivalent
AF.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `URLEncoding.default`
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))
// https://httpbin.org/get?foo=bar
URL编码参数的POST请求(POST Request With URL-Encoded Parameters):
let parameters: [String: [String]] = [
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
// All three of these calls are equivalent
AF.request("https://httpbin.org/post", method: .post, parameters: parameters)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))
// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"
配置编码过后的值的排序
从 Swift 4.2 开始,Swift 的 Dictionary
类型所使用的哈希算法会在运行时会产生随机的内部排序,这就使得每次启动应用程序可能会有所不同。这样会导致编码过后的参数顺序有所改变,从而影响缓存和其他行为。URLEncodedFormEncoder
将会对编码过后的键值对(key-value pairs)进行排序。这将会为所有 Encodable
类型提供一个恒定的顺序的输出,他可能会与该类型实际的编码顺序不符(译者:自己写的参数的顺序,当然这个顺序在 Dictionary
是随机的,和编码过后被排序过得参数顺序可能会不匹配)。你可以通过设置 alphabetizeKeyValuePairs
为 false
来返回实际的参数顺序,尽管这样做的话可能会拥有随机的 Dictionary
顺序。
你可以创建一个你自己的 URLEncodedFormParameterEncoder
在传递进来 URLEncodedFormEncoder
的初始化方法中指定你所期望的 alphabetizeKeyValuePairs
的值:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(alphabetizeKeyValuePairs: false))
配置 Array
参数的编码(Configuring the Encoding of Array
Parameters)
由于没有发布过任何有关如何编码集合类型的规范,Alamofire 依照惯例,将 []
添加到数组名字的后面(foo[]=1&foo[]=2
),对于嵌套字典, 则将被方括号包围的key添加到的后面(foo[bar]=baz
)。
URLEncodedFormEncoder.ArrayEncoding
这个枚举提供了以下的编码 Array
参数的方式:
-
.brackets
- 为每一个数组里面的值添加一个空的方括号的集合,这个也是默认的选项。 -
.noBrackets
- 没有添加方括号,按照原样进行编码。
Alamofire 默认的使用 .brackets
去编码 Array
, 这样的话 foo = [1, 2]
就会被编码成 foo[]=1&foo[]=2
。
使用 .noBrackets
编码的话就会把 foo=[1, 2]
编码成 foo=1&foo=2
。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 ArrayEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(arrayEncoding: .noBrackets))
配置 Bool
类型参数的编码方式(Configuring the Encoding of Bool Parameters)
URLEncodedFormEncoder.BoolEncoding
枚举提供了以下几种用于编码 Bool
类型参数的方式:
-
.numeric
- 把true
编码为1
,把false
编码为0
。这也是默认的方式。 -
.literal
- 把true
和false
编码为文字。
Alamofire 默认的使用 .numeric
的这种方式。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 BoolEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(boolEncoding: .numeric))
配置 Data
类型的参数编码
DataEncoding
包括以下几种编码 Data
参数类型的方式:
-
.deferredToData
- 使用Data
的本地的Encodable
支持。 -
.base64
- 把Data
编码为 Base 64 编码格式的字符串。这是默认的方式。 -
.custom((Data) -> throws -> String)
- 使用提供的闭包(closure)进行编码。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 DataEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dataEncoding: .base64))
配置 Date
类型的参数编码:
鉴于有大量的方法可以吧 Date
编码称为 String
,DateEncoding
包括了以下这些方式来编码 Date
参数:
-
.deferredToDate
- 使用Date
的本地的Encodable
支持,这是默认的方式。 -
.secondsSince1970
- 把Date
编码为从1970年1月1日的午夜开始到现在的秒数。 -
.millisecondsSince1970
- 把Date
编码为从1970年1月1日的午夜开始到现在的毫秒数。 -
.iso8610
- 根据 ISO 8610 和 RFC3339 标准对Date
进行编码。 -
formatted(DateFormatter)
- 使用给定的DateFormatter
对Date
进行编码。 -
custom((Date) -> throws -> String)
- 使用给定的闭包(closure)对Date
进行编码。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 DateEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dateEncoding: .iso8601))
配置键的编码方式(Configuring the Encoding of Coding Keys)
由于参数键的样式多钟多样,KeyEncoding
提供以下方法去编码使用驼峰命名法(lowerCamelCase)的键:
-
.useDefaultKeys
- 使用每种类型指定的键。这个是默认的方式。 -
.convertToSnakeCase
- 把键编码为下划线(译者:snake是蛇形)样式:oneTwoThree
变成one_two_three
。 -
.convertToKebabCase
- 把键编码为横线(译者:kebab是烤串)样式:oneTwoThree
变成one-two-three
。 -
.capitalized
- 仅仅大写第一个字母,又叫做UpperCamelCase
:oneTwoThree
变成OneTwoThree
。 -
.uppercased
- 大写所有字母:oneTwoThree
变成ONETWOTHREE
。 -
lowercased
- 小写所有字母:oneTwoThree
变成onetwothree
。 -
.custom((String) -> String)
- 使用给定的闭包(closure)进行编码。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 KeyEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(keyEncoding: .convertToSnakeCase))
配置空格编码(Configuring the Encoding of Spaces)
比较早的编码器们使用 +
去编码空格,许多服务器至今仍然希望使用这种编码而不是使用现代的百分号编码,因此 Alamofire 包涵了以下的方式对空格进行编码:
-
.percentEscaped
- 通过应用标砖的百分比转码对空格字符进行编码。" "
被编码成"%20"
。这个是默认的方式。 -
.plusReplaced
- 把" "
替换+
。" "
被编码成+
。
你可以创建你自己的 URLEncodedFormParameterEncoder
,然后在需要传递 URLEncodedFormEncoder
的初始化方法中指定 SpaceEncoding
:
let encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(spaceEncoding: .plusReplaced))
第二种编码方式(JSONParameterEncoder)
JSONParameterEncoder
利用Swift的 JSONEncoder
对Encodable
类型的值(values)进行编码,然后把编码后的结果设置到 URLRequest
的 httpBody
中。如果 HTTP header中的 Content-Type
没有设置过的话,会被设置成 aplication/json
。
发送一个JSON编码(JSON-Encoded)参数的网络请求
let parameters: [String: [String]] = [
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.prettyPrinted)
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.sortedKeys)
// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
配置一个自定义的 JSONEncoder
你可以通过传递一个你配置好的 JSONEncoder
实例来自定义 JSONParameterEncoder
的行为:
let encoder = JSONEncoder()
encoder.dateEncoding = .iso8601
encoder.keyEncodingStrategy = .convertToSnakeCase
let parameterEncoder = JSONParameterEncoder(encoder: encoder)
手动为 URLRequest
参数进行编码
ParameterEncoder
的API们也可以用于Alamofire之外直接对 URLRequest
进行参数编码。
let url = URL(string: "https://httpbin.org/get")!
var urlRequest = URLRequest(url: url)
let parameters = ["foo": "bar"]
let encodedURLRequest = try URLEncodedFormParameterEncoder.default.encode(parameters,
into: urlRequest)
HTTP头(HTTP Headers)
Alamofire 拥有自己的的 HTTPHeaders
类型,他是一个保持原有顺序并且不区分大小写的 HTTP头的键值对表示。 HTTPHeader
类型封装了一个单独的键值对并且为通用的HTTP头(headers)提供了许多种静态的值。
为一个 Request
添加一个自定义的 HTTPHeaders
就像给一个 request
的方法传递一个值那么简单:
let headers: HTTPHeaders = [
"Authorization": "Basic VXNlcm5hbWU6UGFzc3dvcmQ=",
"Accept": "application/json"
]
AF.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
debugPrint(response)
}
HTTPHeaders
也可以使用一个包含有 HTTPHeader
类型值的数组来构建:
let headers: HTTPHeaders = [
.authorization(username: "Username", password: "Password"),
.accept("application/json")
]
AF.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
debugPrint(response)
}
对于 HTTP 头来说,没有变的是,推荐把他们设置到
URLSessionConfiguration
,这样他们就可以提供给任何通过基础的URLSession
所创建的URLSessionTask
之中了。更多的信息请看会话设置(Session Configurations)章节。
默认的Alamofire Session
为每个 Request
提供了默认的头部信息(headers)集合,他们是:
-
Accept-Encoding
,根据RFC 7230 §4.2.3,默认的值是br;q=1.0, gzip;q=0.8, deflate;q=0.6
。 -
Accept-Language
,根据RFC 7231 §5.3.5,默认的是系统上首选的6种语言,格式就像是:en;q=1.0
。 -
User-Agent
,根据RFC 7231 §5.5.3,包含有当前应用的版本信息,举个例子:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0
如果你想自定义这些头部信息(headers),你应该去创建一个自定义的 URLSessionConfiguration
,更新 defaultHTTPHeaders
属性并将配置应用于新的 Session
实例。使用 URLSessionConfiguration.af.default
去自定义你的配置并且保留 Alamofire 默认的头部信息(headers)。
响应验证(Response Validation)
Alamofire 默认的会把所有已经完成的网络请求都视为是成功的,忽略服务端响应的内容。如果响应的状态码(status code)或者 MIME类型不能被接受(unacceptable)(译者:异常状态码或类型错误啥的)的话可以在响应处理(response handler)生成错误之前调用 validate()
函数。
自动校验(Automatic Validation)
validate()
这个API自动验证响应状态码的区间是 200..<300
,如果提供了请求的 Accept
头的话,请求的 Accept
头的信息要与响应的 Content-Type
相匹配。
AF.request("https://httpbin.org/get").validate().responseJSON { response in
debugPrint(response)
}
手动校验(Manual Validatin)
AF.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 let .failure(error):
print(error)
}
}
响应处理方式(Response Handling)
Alamofire 的 DataRequest
和 DownloadRequest
都有相应的响应类型:DataResponse<Success, Failure: Error>
和 DownloadResponse<Success, Failure: Error>
。他们俩都有着相同的组成:一个序列化类型和一个错误类型。默认情况下,所有的响应值都会产生错误类型(即:DataResponse<Success, AFError>
)。Alamofire 在公开的 API 中使用更简单的 AFDataResponse<Success>
和 AFDownloadResponse<Success>
,他们都包涵有 AFError
类型。 UploadRequest
是 DataRequest
的子类,使用同样的 DataResponse
类型。
处理 Alamofire 的 DataRequest
或者 UploadRequest
所产生的 DataResponse
涉及到将诸如 responseJSON
这样的响应处理 链接 到 DataRequest
:
AF.request("https://httpbin.org/get").responseJSON { response in
debugPrint(response)
}
在上面的例子中,responseJSON
处理程序被添加到了 DataRequest
上,在 DataRequest
完成的时候执行一次处理程序。传递给处理程序的是一个闭包(closure),这个闭包接收一个 AFDataResponse<Any>
类型的值,这个值是由响应属性中的 JSONRespnseSerializer
所制造出来的。
这个闭包(closure)作为回调(callback)被添加上去以便在接收到响应的时候去执行处理,这样做比阻塞执行来等待服务器的响应要好的多。请求的结果仅仅在响应闭包(closure)的范围内有效。任何取决于响应的或者从服务器接收的数据的处理都要在响应闭包(closure)中完成。
在 Alamofire 下的网络请求都是异步的。异步编程可能使不熟悉这个概念的程序员感到沮丧,但是异步编程的 好处 是非常多的。
Alamofire 中包涵了6种不同的默认的数据响应处理程序,包括:
// Response Handler - Unserialized Response
func response(queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse<Data?>) -> Void) -> Self
// Response Serializer Handler - Serialize using the passed Serializer
func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void) -> Self
// Response Data Handler - Serialized into Data
func responseData(queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse<Data>) -> Void) -> Self
// Response String Handler - Serialized into String
func responseString(queue: DispatchQueue = .main,
encoding: String.Encoding? = nil,
completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self
// Response JSON Handler - Serialized into Any Using JSONSerialization
func responseJSON(queue: DispatchQueue = .main,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (AFDataResponse<Any>) -> Void) -> Self
// Response Decodable Handler - Serialized into Decodable Type
func responseDecodable<T: Decodable>(of type: T.Type = T.self,
queue: DispatchQueue = .main,
decoder: DataDecoder = JSONDecoder(),
completionHandler: @escaping (AFDataResponse<T>) -> Void) -> Self
没有任何一个响应处理程序对从服务器回来的 HTTPURLResponse
执行校验。
举个例子:响应的状态码在
400..<500
和500..<600
的范围内并不会自动触发任何Error
。Alamofire 采用响应校验(Response Validation)(上文提到的) 方式来实现校验。(译者:所以处理函数中并不做任何响应的校验)
响应处理程序(Response Handler)
response
处理程序并不校验任何响应数据。他仅仅直接从 URLSessionDelegate
转发所有的信息(infomation)。这就是 Alamofire 等效于使用 cURL
去执行 Request
。
AF.request("https://httpbin.org/get").response { response in
debugPrint("Response: \(response)")
}
我们强烈建议您去
Response
和Result
类型的其他的响应序列化工具(译者:我的感觉是不建议直接用response,因为提供了6个方法,建议使用其他更具体好用的方法吧)。
响应 Data 数据的处理程序(Response Data Handler)
responseData
处理程序使用 DataResponseSerializer
去提取和校验服务器返回的 Data
数据。如果没有错误发生并且返回了 Data
数据。那么响应中的 Result
将会是 .success
的并且它里面的 value
会变成服务器返回的 Data
数据。
AF.request("https://httpbin.org/get").responseData { response in
debugPrint("Response: \(response)")
}
响应字符数据的处理程序(Response String Handler)
responseString
处理程序使用了 StringResponseSerializer
去把服务器返回的 Data
数据转换成指定编码的 String
。如果没有错误产生并且服务端返回的 数据成功的被序列化为 String
,相应的结果将会是 .success
并且结果的 value
值将会是 String
类型。
AF.request("https://httpbin.org/get").responseString { response in
debugPrint("Response: \(response)")
}
如果没有指定任何编码方式,Alamofire 将会使用服务端返回的
HTTPURLResponse
中指定的文本编码。如果无法根据服务器响应确定文本编码,默认的是采用.isoLatin1
。
响应JSON数据的处理程序(Response JSON Handler)
responseJSON
处理程序利用 JSONResponseSerializer
去将服务端返回的 Data
数据转换成使用指定 JSONSerialization.ReadingOptions
的 Any
类型的数据。如果没有错误发生并且成功的将服务端的数据序列化成了JSON对象(JSON object),那么响应 AFResult
将会是 .sucess
并且他的 value
将会是 Any
类型。
AF.request("https://httpbin.org/get").responseJSON { response in
debugPrint("Response: \(response)")
}
respnseJSON
中使用的 JSON 序列化是使用的Foundation
框架中的JSONSerialization
来完成处理的。
响应 Decodable
数据的处理程序(Response Decodable
Handler)
responseDecodable
数据处理程序使用 DecodableResponseSerializer
去将服务端返回的 Data
数据转换成传参进来的使用指定 DataDecoder
(一个可以从数据(Data
)解码的解码器(Decoder
)的协议) 的 Decodable
类型数据。如果没有错误产生并且成功的将服务端返回的数据解码成了一个 Decodable
类型,那么响应的 Result
将会是 .success
的并且 他的 value
将会是传参进来的类型。
struct HTTPBinResponse: Decodable { let url: String }
AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint("Response: \(response)")
}
链式的响应处理程序(Chained Response Handlers)
响应处理程序也可以被串联起来:
Alamofire.request("https://httpbin.org/get")
.responseString { response in
print("Response String: \(response.value)")
}
.responseJSON { response in
print("Response JSON: \(response.value)")
}
很重要的一点是,如果在同一个
Request
使用多重响应处理程序的话,需要服务器对返回数据多次序列化,每个响应处理程序处理一次。最好不要在同一个Request
上使用多重响应处理程序,特别是在生产环境中。这种做做仅仅应该在开发环境或者没有其他的更好的选择的情况下去做。
相应处理程序队列(Response Handler Queue)
给响应处理程序传递的闭包(closures)默认是在 .main
队列上执行的,但是也可以指定一个 DispatchQueue
给需要执行的闭包(closure)。事实上,序列化的工作(把 Data
数据转换成其他类型的数据)总是在一个后台队列(background queue)中执行的。
let utilityQueue = DispatchQueue.global(qos: .utility)
AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in
print("Executed on utility queue.")
debugPrint(response)
}
响应的缓存(Response Caching)
响应缓存是由 URLCache
在系统级别进行处理的。他提供了一个内存(in-memory)和磁盘(on-disk)缓存的结合体,使您可以操控内存(in-memory)和磁盘(on-disk)部分的大小。
Alamofire默认的使用
URLCache.shared
这个实例进行自定义的URLCache
实例的使用,请看会话配置(Session Configuration)章节。
认证方式(Authentication)
认证方式(Authentication)是由 URLCredential
和 URLAuthenticationChallenge
在系统框架级别进行操作的。
这些认证方式的 API 适用于提示授权的服务器,并非一般用于需要
Authenticate
(译者:身份验证) 和 等效的请求头的服务器。
支持的验证方式(Supported Authentication Schemes)
HTTP Basic 授权方式(HTTP Basic Authentication)
当使用 URLAuthenticationChallenge
质询的时候,在适当的时候Request
上的 authenticate
方法会自动提供 URLCredential
:
let user = "user"
let password = "password"
AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(username: user, password: password)
.responseJSON { response in
debugPrint(response)
}
URLCredential
授权方式(Authentication with URLCredential
)
let user = "user"
let password = "password"
let credential = URLCredential(user: user, password: password, persistence: .forSession)
AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(with: credential)
.responseJSON { response in
debugPrint(response)
}
需要注意的很重要的一点是,当你使用
URLCredential
来进行认证的时候,如果服务器发出了质询,那么底层的URLSession
实际上最终会发出两个请求(requests)。第一个请求将会不包括可能触发服务器质询的凭据(credential)。Alamofire来接收这个服务器的质询,并把凭证(credential)加上去,再由底层的的URLSession
重新发送请求(request)。
手动授权(Manual Authentication)
如果你要与一个始终需要 Authenticate
或者类似的请求头的 API 通信而没有提示的时候,你可以手动的加上:
let user = "user"
let password = "password"
let headers: HTTPHeaders = [.authorization(username: user, password: password)]
AF.request("https://httpbin.org/basic-auth/user/password", headers: headers)
.responseJSON { response in
debugPrint(response)
}
但是,必须作为所有请求的一部分的请求头(headers)通常更好的被处理为自定义的 URLSessionConfiguration
的一部分,或者使用一个 RequestAdapter
。(译者注:这些在高级用法中有提到)
下载数据到文件(Downloading Data to a File)
除了把获取到的数据放入内存,Alamofire 还提供了 Session.download
,DownloadRequest
,和 DownloadResponse<Success, Failure: Error>
这些 API 去方便的下载数据到磁盘。下载到内存的行为非常适合像大多数 JSON API 的很小的响应信息,而获取大的资源像是图片和视频,就应该下载到磁盘以缓解应用的内存空间。
AF.download("https://httpbin.org/image/png").responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
DownloadRequest
具有与DataRequest
相同的大多数的响应处理程序(response
handlers)。但是,由于他把数据下载到磁盘,序列化响应会涉及到从磁盘读取数据,这样也可能导致往内存中读取了大量的数据。在涉设计你自己的数据下载处理的时候要记住这些,这很重要。
文件下载的位置(Download File Destination)
所有下载的数据最初都存放在系统的临时目录里(temporary directory)。这些数据最终将会被系统在某个时间点删除掉,因此如果有些情况需要这些文件存在的时间更长一些的话,把他们移动到某个其他的地方是很重要的。
你可以提供一个 Destination
闭包(closure)去把文件从临时文件目录移动到最终的位置上去。在临时文件被真正的移动到 destinationURL
之前,闭包(closure)中指定的 Options
会被执行,目前支持的两种 Options
是:
-
. createIntermediateDirectories
- 如果指定目录,则为目标目录创建中间目录。 -
.removePreviousFile
- 如果指定,则从目标目录中删除之前的文件。
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("image.png")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF.download("https://httpbin.org/image/png", to: destination).response { response in
debugPrint(response)
if response.error == nil, let imagePath = response.fileURL?.path {
let image = UIImage(contentsOfFile: imagePath)
}
}
你也可以使用建议的下载位置的 API:
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
AF.download("https://httpbin.org/image/png", to: destination)
下载进度(download Progress)
很多时候,给用户报告下载进度是很有用的。任何的 DownloadRequest
都可以利用 downloadProgress
API 来报告下载进度。
AF.download("https://httpbin.org/image/png")
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
仅当服务器返回
Content-Length
头的时候,URLSession
的进度报告相关的 API 才起作用,对于 ALamofire 也是这样的。没有这个头(header)的话,进度将停留在0.0
,下载完成,进度会跳到1.0
。
downloadProgress
API 也可以传入一个 queue
参数来定义哪一个 DispatchQueue
是下载进度闭包(closure)所调用的的所在。
let progressQueue = DispatchQueue(label: "com.alamofire.progressQueue", qos: .utility)
AF.download("https://httpbin.org/image/png")
.downloadProgress(queue: progressQueue) { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
取消和重新下载(Canceling and Resuming a Download)
除了所有的 Request
类都有 cancel()
API 之外,DownloadRequest
还可以生成恢复数据(resume data),这些恢复数据可以被用来在稍后的时候重新下载数据。这个 API 有两种形式:cancel(producingResumeData: Bool)
可以控制是否生成恢复数据,但是他仅仅对 DownloadResponse
有用;cancel(byProducingResumeData:(_ resumeData: Data?) -> Void)
有着相同的作用,但是他可以让恢复数据在完成处理程序(completion handler)中可用。
如果一个 DownloadRequest
被取消了或者被中断了,底层的 URLSessinDownloadTask
可能会生成回复数据。如果生成了恢复数据,这个恢复数据可以再次被用来在中断的地方重新开始 DownloadRequest
。
极其重要: 在苹果平台的某些版本(iOS10 - 10.2,macOS10.12-10.12.2, tvOS10-10.1,watchOS3-3.1.1 )中,
resumeData
在后台的URLSessionConfiguration
中是损坏的。在底层的resumeData
生成逻辑中存在错误,错误的数据被写入,导致了始终无法通过resumeData
重新进行下载。关于更多的这个bug的信息和可能的解决办法,请看 Stack Overflow post。
var resumeData: Data!
let download = AF.download("https://httpbin.org/image/png").responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
// download.cancel(producingResumeData: true) // Makes resumeData available in response only.
download.cancel { data in
resumeData = data
}
AF.download(resumingWith: resumeData).responseData { response in
if let data = response.value {
let image = UIImage(data: data)
}
}
上传数据到服务器(Uploading Data to a Server)
当发送相对少量的使用 JSON 或者 URL 编码的参数到服务器的时候,request()
APIs 通常就够用了。如果你需要发送从内存中、通过文件 URL
、或者 InputStream
来的大量的 Data
的时候,upload()
APIs 可能是你想要使用的。
上传数据(Uploading Data)
let data = Data("data".utf8)
AF.upload(data, to: "https://httpbin.org/post").responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint(response)
}
上传文件(Uploading a File)
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
AF.upload(fileURL, to: "https://httpbin.org/post").responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint(response)
}
上传多部分表格数据(Uploading Multipart Form Data)
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(Data("one".utf8), withName: "one")
multipartFormData.append(Data("two".utf8), withName: "two")
}, to: "https://httpbin.org/post")
.responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint(response)
}
上传进度(Upload Progress)
当你的用户正在等待他们上传数据完成的时候,有时候展示给用户上传进度对于他们是很便利的。任何的 UploadRequest
使用 uploadProgress
和 downloadProgress
的时候都可以报告上传数据的进度和响应数据下载的进度。
let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
AF.upload(fileURL, to: "https://httpbin.org/post")
.uploadProgress { progress in
print("Upload Progress: \(progress.fractionCompleted)")
}
.downloadProgress { progress in
print("Download Progress: \(progress.fractionCompleted)")
}
.responseDecodable(of: HTTPBinResponse.self) { response in
debugPrint(response)
}
从服务器来的流式传输数据(Streaming Data from a Server)
使用流传输而不是积累不断接收到的数据,可以更好的服务于长时间下载大型的文件或者连接服务器。Alamofire 提供了 DataStreamRequest
这个类型以及相关的 API 去处理这种用法。尽管这样就提供了好多像其他的 Request
一样的 API,但是它仍然有几个主要的区别。最明显的, DataStreamRequest
不在内存中积累数据或者将数据存入磁盘(disk)。相反的是,它在数据到达的时候反复调用 responseStream
闭包(closures)。在连接完成或者收到错误的时候会再次调用相同的闭包。
每个 Handler
闭包(closure)都捕获一个 Stream
值,其中包括一个 Event
以及可以取消请求的 CancellationToken
。
public struct Stream<Success, Failure: Error> {
/// Latest `Event` from the stream.
public let event: Event<Success, Failure>
/// Token used to cancel the stream.
public let token: CancellationToken
/// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`.
public func cancel() {
token.cancel()
}
}
一个 Event`` 就是一个代表两种流状态的
enum```。
public enum Event<Success, Failure: Error> {
/// Output produced every time the instance receives additional `Data`. The associated value contains the
/// `Result` of processing the incoming `Data`.
case stream(Result<Success, Failure>)
/// Output produced when the instance has completed, whether due to stream end, cancellation, or an error.
/// Associated `Completion` value contains the final state.
case complete(Completion)
}
完成的时候,Completion
值将会包含流结束时候的 DataStreamRequest
的状态。
public struct Completion {
/// Last `URLRequest` issued by the instance.
public let request: URLRequest?
/// Last `HTTPURLResponse` received by the instance.
public let response: HTTPURLResponse?
/// Last `URLSessionTaskMetrics` produced for the instance.
public let metrics: URLSessionTaskMetrics?
/// `AFError` produced for the instance, if any.
public let error: AFError?
}
流数据(Streaming Data
)
可以想其他的 Alamofire 请求一样去完成从服务器请求流数据,但是多了一个 Handler
闭包(closure)。
func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler<Data, Never>) -> Self
提供的 queue
将会是 handler
闭包所在的位置。
AF.streamRequest(...).responseStream { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(data):
print(data)
}
case let .complete(completion):
print(completion)
}
}
在上面的例子中处理
.failure
这种Result
的情况是不必要的,接收Data
不可能是失败的。
流式的字符串(Streaming String
s)
像流式 Data
一样,String
可以添加 Handler
来处理流式字符串数据。
func responseStreamString(on queue: DispatchQueue = .main,
stream: @escaping StreamHandler<String, Never>) -> Self
String
值被解码为 UTF8
,这种解码不会失败。
AF.streamRequest(...).responseStreamString { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(string):
print(string)
}
case let .complete(completion):
print(completion)
}
}
流式 Decodable
值(Streaming Decodable
值)
传入的流式 Data
值可以使用 responseStreamDecodable
转换成任意的 Decodable
。
func responseStreamDecodable<T: Decodable>(of type: T.Type = T.self,
on queue: DispatchQueue = .main,
using decoder: DataDecoder = JSONDecoder(),
preprocessor: DataPreprocessor = PassthroughPreprocessor(),
stream: @escaping Handler<T, AFError>) -> Self
解码(Decoding)失败并不会终止流,而是在 Result
的 Output
中生成一个 AFError
。
AF.streamRequest(...).responseStreamDecodable(of: SomeType.self) { stream in
switch stream.event {
case let .stream(result):
switch result {
case let .success(value):
print(value)
case let .failure(error):
print(error)
}
case let .complete(completion):
print(completion)
}
}
生成一个输入流(Producing an InputStream)
除了使用 StreamHandler
闭包处理传入的 Data
之外, DataStreamRequest
可以生成一个可以读取到达的字节的 InputStream
。
func asInputStream(bufferSize: Int = 1024) -> InputStream
通过这种方式创建的 InputStream
必须在读取开始之前调用 open()
,或者将其传递给自动打开流的 API。一旦从这个方法返回了 InputStream
,这个方法的调用者有责任去保持 InputStream
存活,并且在读取完之后调用 close()
。
let inputStream = AF.streamRequest(...)
.responseStream { output in
...
}
.asInputStream()
取消(Cancellation)
DataStreamRequest
们可以用4种方式来取消。第一种,像所有其他的 Alamofire 的 Request
一样,DataStreamRequest
可以调用 cancel()
来取消底层(underlying)的任务(task)并且完成流。
let request = AF.streamRequest(...).responseStream(...)
...
request.cancel()
第二种,DataStreamRequest
们可以在 DataStreamSerializer
发生错误的时候被自动的取消。这种行为默认是被禁止的但是可以通过 automaticallyCancelOnStreamError
这个参数在创建请求的时候允许这种行为。
AF.streamRequest(..., automaticallyCancelOnStreamError: true).responseStream(...)
第三种,DataStreamRequest
们将会在 Handler
闭包(closure)抛出错误的时候被取消。之后,这个错误会被存入请求之中并且在 Completion
值中可用。
AF.streamRequest(...).responseStream { stream in
// Process stream.
throw SomeError() // Cancels request.
}
最后一种,DataStreamRequest
们可以使用 Stream
值的 cancel()
方法来取消。
AF.streamRequest(...).responseStream { stream in
// Decide to cancel request.
stream.cancel()
}
统计指标(Statistical Metrics)
URLSessionTaskMetrics
Akanifure 为每一个 Request
收集 URLSessionTaskMetrics
。URLSessionTaskMetrics
封装了一些关于基础的网络连接和请求响应事件的奇妙的统计信息。
AF.request("https://httpbin.org/get").responseJSON { response in
print(response.metrics)
}
由于
FB7624529
,目前收集 watchOS 的URLSessionTaskMetrics
是不可用的。
cURL 命令输出(cURL Command Output)
调试平台的错误可能让人蛋疼,但是谢天谢地,Alamofire 的 Request
类型可以产生等效的 cURL 命令来方便调试。由于 Alamofire 的 Request
创建是异步的这个特性,这个API有同步(synchronous)的和一步的(asynchronous)两个版本。为了尽快的获取 cURL 命令,你可以链接一个 CURLDescription
在request上:
AF.request("https://httpbin.org/get")
.cURLDescription { description in
print(description)
}
.responseJSON { response in
debugPrint(response.metrics)
}
这个就制造出来了:
$ curl -v \
-X GET \
-H "Accept-Language: en;q=1.0" \
-H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
-H "User-Agent: Demo/1.0 (com.demo.Demo; build:1; iOS 13.0.0) Alamofire/1.0" \
"https://httpbin.org/get"