测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布最新发布 | 2017年10月 |
SwiftSwift版本 | echo "3.0" > .swift-version |
SPM支持SPM | ✗ |
由 Jose Antonio García Yáñez 维护。
依赖项 | |
Alamofire | ~> 4.0 |
ResponseDetective | ~> 1.0 |
Result | ~> 3.0.0 |
Alamofire是用Swift编写的HTTP网络库。
为了使Alamofire专注于核心网络实现,由Alamofire软件基金会创建了额外的组件库,以向Alamofire生态系统带来更多功能。
UIImage
和UIImageView
扩展、自定义图像过滤器、自动清除内存缓存以及基于优先级的图像下载系统。URLSession
实例。Swift包管理器 是一个用于自动分发Swift代码的工具,并集成在 swift
编译器中。它处于早期开发阶段,但Alamofire在支持的平台上支持使用它。
设置好Swift包后,将Alamofire作为依赖项添加的方法与将其添加到您的 Package.swift
中的 dependencies
值相同。
dependencies: [
.Package(url: "https://github.com/Alamofire/Alamofire.git", majorVersion: 4)
]
如果您不想使用上述任何依赖项管理器,可以手动将Alamofire集成到您的项目中。
打开终端,进入您的顶级项目目录,如果您的项目不是作为git仓库初始化的,请运行以下命令:
$ git init
通过运行以下命令将Alamofire作为git 子模块 添加
$ git submodule add https://github.com/Alamofire/Alamofire.git
打开新的 Alamofire
文件夹,并将 Alamofire.xcodeproj
拖放到您应用程序Xcode项目的项目导航器中。
它应该嵌套在您的应用程序的蓝色项目图标下面。它位于所有其他Xcode组之上或之下无关紧要。
在项目导航器中选择 Alamofire.xcodeproj
并检查部署目标与您的应用程序目标相同。
然后,在项目导航器中选择您的应用程序项目(蓝色项目图标),导航到目标配置窗口,并在侧边栏的“Targets”标题下选择应用程序目标。
在该窗口顶部的选项卡栏中,打开“通用”面板。
在“嵌入式二进制”部分下单击 +
按钮。
您将看到两个不同的 Alamofire.xcodeproj
文件夹,每个文件夹中都有一个嵌套在 Products
文件夹中的不同版本的 Alamofire.framework
。
您可以选择哪个
Products
文件夹无关紧要,但您选择的Alamofire.framework
是顶部还是底部很重要。
对于iOS,选择顶部的 Alamofire.framework
,对于OS X,选择底部的 ones。
您可以通过检查项目的构建日志来验证您选择了哪一个。构建目标将列为目标
Alamofire iOS
、Alamofire macOS
、Alamofire tvOS
或Alamofire watchOS
。
就是这样!
Alamofire.framework
会自动作为目标依赖项、链接框架和嵌入式框架添加到复制文件的构建阶段,这就是在模拟器和设备上构建所需要的一切。
import Alamofire
Alamofire.request("https://httpbin.org/get")
要在Alamofire中处理发起的 Request
的 Response
,需要将响应处理程序链接到 Request
。
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
}
}
在上面的示例中,将 responseJSON
处理程序附加到要执行的 Request
上,一旦 Request
完成,就会执行。而不是阻止执行以等待从服务器收到的响应,指定了一个形式的回调来处理收到的响应,即闭包。请求的结果仅在响应闭包的作用域内有效。任何依赖于响应或从服务器收到的数据执行的执行必须在响应闭包内完成。
Alamofire中的网络传输是异步进行的。异步编程可能会让不熟悉该概念的程序员感到沮丧,但以这种方式进行的原因也很有道理。
Alamofire默认包含五个不同的响应处理器,包括
// 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
这些响应处理器都不会对从服务器返回的HTTPURLResponse
进行任何验证。
例如,状态代码在
400..<500
和500..<600
范围内不会自动触发错误。Alamofire通过响应验证方法链来实现这一点。
响应处理器不会评估任何响应数据。它只是将来自URL会话代理的全部信息直接传递出去。它是Alamofire中使用cURL
执行请求的等效方式。
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)")
}
}
我们强烈建议您利用其他响应序列化器,并利用
Response
和Result
类型。
响应数据处理器使用responseDataSerializer
Data。如果没有发生错误并且返回了Data
,则响应Result
将为.success
,并且值为类型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)")
}
}
响应字符串处理器使用responseStringSerializer
将服务器返回的Data
转换为带有指定编码的字符串。如果没有发生错误并且服务器数据成功序列化为字符串,则响应Result
将为.success
,并且值为类型String
。
Alamofire.request("https://httpbin.org/get").responseString { response in
print("Success: \(response.result.isSuccess)")
print("Response String: \(response.result.value)")
}
如果没有指定编码,Alamofire将使用服务器中指定的
HTTPURLResponse
中的文本编码。如果服务器响应无法确定文本编码,则默认为.isoLatin1
。
响应JSON处理器使用responseJSONSerializer
使用指定的JSONSerialization.ReadingOptions
将服务器返回的Data
转换为类型为Any
。如果没有发生错误并且服务器数据成功序列化为JSON对象,则响应Result
将为.success
,并且值为类型Any
。
Alamofire.request("https://httpbin.org/get").responseJSON { response in
debugPrint(response)
if let json = response.result.value {
print("JSON: \(json)")
}
}
所有JSON序列化都由
Foundation
框架中的JSONSerialization
API处理。
响应处理器甚至可以链式使用
Alamofire.request("https://httpbin.org/get")
.responseString { response in
print("Response String: \(response.result.value)")
}
.responseJSON { response in
print("Response JSON: \(response.result.value)")
}
需要注意的是,在同一个
Request
上使用多个响应处理器需要将服务器数据序列化多次。对于每个响应处理器,序列化一次。
默认情况下,响应处理器在主分发队列上执行。然而,也可以提供自定义分发队列。
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会认为任何完成的请求都是成功的,无论响应内容如何。在响应处理器之前调用validate
会在响应有不可接受的状态代码或MIME类型时生成错误。
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
范围内,并且响应的Content-Type
头与请求的Accept
头相匹配(如果提供了的话)。
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
枚举列出了在RFC 7231 §4.3中定义的HTTP方法
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
API的method
参数传递
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)
Alamofire.request
方法参数默认为.get
。
Alamofire支持三种类型的参数编码,包括:URL
、JSON
和PropertyList
。它还可以支持任何遵守ParameterEncoding
协议的自定义编码。
URLEncoding
类型创建一个url-encoded查询字符串,将其设置为或附加到任何现有的URL查询字符串,或设置为URL请求的HTTP正文。查询字符串是设置为或附加到任何现有URL查询字符串,还是设置为HTTP正文,取决于编码的Destination
。枚举的Destination
有三个情况
.methodDependent
- 对于GET
、HEAD
和DELETE
请求,将编码的查询字符串结果应用于现有查询字符串,并设置为任何其他HTTP方法请求的HTTP正文。.queryString
- 将编码的查询字符串结果设置为或附加到现有查询字符串。.httpBody
- 将编码的查询字符串结果设置为URL请求的HTTP正文。带有HTTP正文的编码请求的HTTP头字段的Content-Type
设置为application/x-www-form-urlencoded; charset=utf-8
。由于没有公开的指定如何对集合类型进行编码的规范,因此对数组值(如foo[]=1&foo[]=2
)的键添加[]
的约定,以及对嵌套字典值(如foo[bar]=baz
)的键添加方括号的约定。
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
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
JSONEncoding
类型创建参数对象的JSON表示,将其设置为请求的HTTP正文。编码请求的HTTP头的Content-Type
设置为application/json
。
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"}}
PropertyListEncoding
使用PropertyListSerialization
根据关联的格式和写入选项值创建参数对象的plist表示,将其设置为请求的正文。编码请求的HTTP头的Content-Type
设置为application/x-plist
。
如果提供的ParameterEncoding
类型无法满足您的需求,您可以创建自己的自定义编码。以下是一个快速示例,说明您如何构建一个自定义的JSONStringArrayEncoding
类型来将JSON字符串数组编码到Request
。
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
}
}
可以在创建网络请求之外使用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)
直接在全球request
方法中支持为Request
添加自定义HTTP头。这使得易于将变量更改的HTTP头附加到Request
。
let headers: HTTPHeaders = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
"Accept": "application/json"
]
Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
debugPrint(response)
}
对于不更改的HTTP头,建议将它们设置为
URLSessionConfiguration
,以便自动应用于底层URLSession
创建的任何URLSessionTask
。有关更多信息,请参阅会话管理器配置部分。
默认的Alamofire SessionManager
为每个Request
提供一组默认的HTTP头。这些包括
Accept-Encoding
,默认为gzip;q=1.0, compress;q=0.5
,按照RFC 7230 §4.2.3。Accept-Language
,默认为系统上最多6个首选语言,格式为en;q=1.0
,按照RFC 7231 §5.3.5。User-Agent
头包含关于当前应用程序的版本信息。例如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0
,据RFC 7231 第 5.5.3 节规定。如果您需要自定义这些标头,应创建自定义 URLSessionConfiguration
,更新 defaultHTTPHeaders
属性,并将配置应用于新的 SessionManager
实例。
系统框架级别通过 URLCredential
和 URLAuthenticationChallenge
来处理身份验证。
支持的认证方案
Request
实例上的 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
标头。
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
进行身份验证时,如果服务器发出挑战,基底URLSession
实际会发出两个请求。第一个请求不包括凭证,这可能会触发服务器的挑战。然后,Alamofire 会收到挑战,追加凭证并重试请求。
使用 Alamofire 从服务器获取数据的请求可以将数据下载到内存中或在磁盘上。迄今为止的所有示例中使用的 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)
}
}
如果您需要在应用程序后台运行时下载数据,也应使用
Alamofire.download
API。有关更多信息,请参阅会话管理器配置部分。
您还可以提供 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.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。
let destination = DownloadRequest.suggestedDownloadDestination(for: .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 会话可能会为活动的 DownloadRequest
生成恢复数据。如果发生这种情况,可以重用恢复数据来重新启动 DownloadRequest
,并从上次中断的地方继续下载。恢复数据可以通过下载响应访问,并在尝试重新启动请求时重用。
重要:在所有苹果平台最新发布版本(iOS 10、macOS 10.12、tvOS 10、watchOS 3)中,背景URL会话配置中的
resumeData
存在问题。在resumeData
生成逻辑中存在一个深层错误,数据写入不正确,而下载数据时总会失败。有关错误信息和可能的解决方案,请参阅此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.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
}
}
}
}
当使用JSON或URL编码参数向服务器发送相对较小的数据量时,通常可以使用Alamofire.request
API。如果您需要从文件URL或InputStream
发送大量的数据,则应使用Alamofire.upload
API。
如果您需要在应用处于后台时上传数据,也应使用
Alamofire.upload
API。有关更多信息,请参阅会话管理器配置部分。
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
API报告上传进度和响应数据的下载进度。
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)
}
Alamofire在整个Request
生命周期中收集计时信息,并在所有响应类型上公开作为属性创建一个Timeline
对象。
Alamofire.request("https://httpbin.org/get").responseJSON { response in
print(response.timeline)
}
上述报告以下Timeline
信息
延迟
:0.428秒请求持续时间
:0.428秒序列化持续时间
:0.001秒总持续时间
:0.429秒在iOS和tvOS 10以及macOS 10.12中,苹果引入了新的URLSessionTaskMetrics
API。任务指标封装了有关请求和响应执行的一些非常出色的统计信息。此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"
Alamofire建立在URLSession
和Foundation URL加载系统之上。为了充分利用此框架,建议您熟悉底层网络堆栈的概念和能力。
推荐阅读
顶级方便方法,如Alamofire.request
使用默认的Alamofire.SessionManager
实例,它配置了默认的URLSessionConfiguration
。
因此,以下两个语句是等效的
Alamofire.request("https://httpbin.org/get")
let sessionManager = Alamofire.SessionManager.default
sessionManager.request("https://httpbin.org/get")
应用程序可以创建用于后台和短暂会话的会话管理器,以及用于自定义默认会话配置(例如,对于默认头httpAdditionalHeaders
或请求超时时间timeoutIntervalForRequest
)的新管理器。
let configuration = URLSessionConfiguration.default
let sessionManager = Alamofire.SessionManager(configuration: configuration)
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
let configuration = URLSessionConfiguration.ephemeral
let sessionManager = Alamofire.SessionManager(configuration: configuration)
var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = defaultHeaders
let sessionManager = Alamofire.SessionManager(configuration: configuration)
不推荐在
Authorization
或Content-Type
头中使用此方法。相反,使用顶级Alamofire.request
API 中的headers
参数,以及分别使用URLRequestConvertible
和ParameterEncoding
。
默认情况下,一个 Alamofire SessionManager
实例会创建一个 SessionDelegate
对象来处理由本地 URLSession
生成的各种类型的代理回调。每个代理方法的实现处理了此类调用最常见的使用情况,将复杂性从顶层 API 中抽象出去。然而,高级用户可能因各种原因需要覆盖默认功能。
自定义 SessionDelegate
行为的第一种方法是使用覆盖闭包。每个闭包都允许您覆盖匹配 SessionDelegate
API 的实现,同时仍然使用其他所有 API 的默认实现。这可以轻松地定制代理功能的一部分。以下是一些可用的覆盖闭包示例:
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
以下是一个使用 taskWillPerformHTTPRedirection
来避免跟随任何 apple.com
域的重定向的简短示例。
let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
let delegate: Alamofire.SessionDelegate = sessionManager.delegate
delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
var finalRequest = request
if
let originalRequest = task.originalRequest,
let urlString = originalRequest.url?.urlString,
urlString.contains("apple.com")
{
finalRequest = originalRequest
}
return finalRequest
}
覆盖 SessionDelegate
默认实现的另一种方法是子类化它。子类化允许您完全自定义 API 的行为,或创建一个代理来使用默认实现。创建代理允许您记录事件、发出通知、提供前后钩子实现等。以下是一个子类化 SessionDelegate
并在发生重定向时记录消息的快速示例。
class LoggingSessionDelegate: SessionDelegate {
override func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
print("URLSession will perform HTTP redirection to request: \(request)")
super.urlSession(
session,
task: task,
willPerformHTTPRedirection: response,
newRequest: request,
completionHandler: completionHandler
)
}
}
一般来说,默认实现或覆盖闭包都应提供所需的全部功能。子类化仅应作为最后的手段使用。
请注意,在默认实现中初始化和销毁
subdelegates
。在子类化的情况下,请注意不要引入内存泄露。
request
、download
、upload
或 stream
方法的结果是继承自 Request
的 DataRequest
、DownloadRequest
、UploadRequest
和 StreamRequest
。所有 Request
实例都由拥有会话管理器创建,并且从未直接初始化。
每个子类都有自己的专用方法,例如 authenticate
、validate
、responseJSON
和 uploadProgress
,这些方法都返回调用者实例,以便便于方法链。
请求可以被挂起、恢复和取消
suspend()
:挂起底层任务和dispatch队列。resume()
:恢复底层任务和dispatch队列。如果拥有会话管理器未将 startRequestsImmediately
设置为 true
,请求必须调用 resume()
才能开始。cancel()
:取消底层任务,传递一个错误给任何已注册的响应处理程序。随着应用的扩展,您在构建网络堆栈时 Adopt 常规的模和实践是非常重要的。该设计的重要组成部分是如何路由您的请求。Alamofire 的 URLConvertible
和 URLRequestConvertible
协议以及 Router
设计模式正是为此而提供。
采用 URLConvertible
协议的类型可用于构建 URL,然后将这些 URL 用于内部构建 URL 请求。默认情况下,String
、URL
和 URLComponents
符合 URLConvertible
,允许将任何这些作为 url
参数传递给 request
、upload
和 download
方法。
let urlString = "https://httpbin.org/post"
Alamofire.request(urlString, method: .post)
let url = URL(string: urlString)!
Alamofire.request(url, method: .post)
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
Alamofire.request(urlComponents, method: .post)
鼓励与网络应用有重大交互的应用程序自己的类型符合 URLConvertible
,作为将特定于域的模型映射到服务器资源的便捷方式。
extension User: URLConvertible {
static let baseURLString = "https://example.com"
func asURL() throws -> URL {
let urlString = User.baseURLString + "/users/\(username)/"
return try urlString.asURL()
}
}
let user = User(username: "mattt")
Alamofire.request(user) // https://example.com/users/mattt
采用URLRequestConvertible
协议的类型可以用来构建URL请求。URLRequest
默认符合URLRequestConvertible
协议,允许它直接传递给request
、upload
和download
方法(这是为单个请求指定自定义HTTP主体的推荐方式)。
let url = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let parameters = ["foo": "bar"]
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
} catch {
// No-op
}
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
Alamofire.request(urlRequest)
强烈建议与Web应用程序以重要方式交互的应用程序将自定义类型定义为符合URLRequestConvertible
,以确保请求端点的一致性。这种方法可以用来抽象服务器端的不一致性,提供类型安全路由,以及管理认证凭据和其他状态。
enum Router: URLRequestConvertible {
case search(query: String, page: Int)
static let baseURLString = "https://example.com"
static let perPage = 50
// MARK: URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let result: (path: String, parameters: Parameters) = {
switch self {
case let .search(query, page) where page > 0:
return ("/search", ["q": query, "offset": Router.perPage * page])
case let .search(query, _):
return ("/search", ["q": query])
}
}()
let url = try Router.baseURLString.asURL()
let urlRequest = URLRequest(url: url.appendingPathComponent(result.path))
return try URLEncoding.default.encode(urlRequest, with: result.parameters)
}
}
Alamofire.request(Router.search(query: "foo bar", page: 1)) // https://example.com/search?q=foo%20bar&offset=50
import Alamofire
enum Router: URLRequestConvertible {
case createUser(parameters: Parameters)
case readUser(username: String)
case updateUser(username: String, parameters: Parameters)
case destroyUser(username: String)
static let baseURLString = "https://example.com"
var method: HTTPMethod {
switch self {
case .createUser:
return .post
case .readUser:
return .get
case .updateUser:
return .put
case .destroyUser:
return .delete
}
}
var path: String {
switch self {
case .createUser:
return "/users"
case .readUser(let username):
return "/users/\(username)"
case .updateUser(let username, _):
return "/users/\(username)"
case .destroyUser(let username):
return "/users/\(username)"
}
}
// MARK: URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try Router.baseURLString.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = method.rawValue
switch self {
case .createUser(let parameters):
urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
case .updateUser(_, let parameters):
urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
default:
break
}
return urlRequest
}
}
Alamofire.request(Router.readUser("mattt")) // GET https://example.com/users/mattt
如今的大多数Web服务背后都有一套某种形式的认证系统。其中较为常见的一种是OAuth。这通常涉及生成一个访问令牌,授权您的应用程序或用户调用各种支持的Web服务。虽然创建这些初始访问令牌可能很耗时,但当您的访问令牌过期并需要获取一个新的时,可能更加复杂。需要考虑许多线程安全问题。
RequestAdapter
和RequestRetrier
协议的创建是为了使为特定一套Web服务创建线程安全的认证系统变得更加容易。
RequestAdapter
协议允许在创建请求之前对SessionManager
上发出的每个Request
进行检查和适配。使用适配器的一个非常具体的方式是为某类型认证后的请求添加一个Authorization
头。
class AccessTokenAdapter: RequestAdapter {
private let accessToken: String
init(accessToken: String) {
self.accessToken = accessToken
}
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
}
return urlRequest
}
}
let sessionManager = SessionManager()
sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")
sessionManager.request("https://httpbin.org/get")
RequestRetrier
协议允许在执行过程中遇到Error
的Request
进行重试。当同时使用RequestAdapter
和RequestRetrier
协议时,您可以创建用于OAuth1、OAuth2、Basic Auth甚至是指数退避重试策略的凭证刷新系统。可能性是无限的。以下是一个实现OAuth2访问令牌刷新流程的示例。
免责声明:这不是一个全局的
OAuth2
解决方案。它只是一个示例,说明可以如何使用RequestAdapter
与RequestRetrier
结合来创建线程安全的刷新系统。
重申一下,请不要复制此示例代码并将其放入生产应用程序中。这只是一个示例。每个认证系统都必须针对特定平台和认证类型进行定制。
class OAuth2Handler: RequestAdapter, RequestRetrier {
private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void
private let sessionManager: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
private let lock = NSLock()
private var clientID: String
private var baseURLString: String
private var accessToken: String
private var refreshToken: String
private var isRefreshing = false
private var requestsToRetry: [RequestRetryCompletion] = []
// MARK: - Initialization
public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
self.clientID = clientID
self.baseURLString = baseURLString
self.accessToken = accessToken
self.refreshToken = refreshToken
}
// MARK: - RequestAdapter
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
return urlRequest
}
return urlRequest
}
// MARK: - RequestRetrier
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
if let accessToken = accessToken, let refreshToken = refreshToken {
strongSelf.accessToken = accessToken
strongSelf.refreshToken = refreshToken
}
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
}
}
} else {
completion(false, 0.0)
}
}
// MARK: - Private - Refresh Tokens
private func refreshTokens(completion: @escaping RefreshCompletion) {
guard !isRefreshing else { return }
isRefreshing = true
let urlString = "\(baseURLString)/oauth2/token"
let parameters: [String: Any] = [
"access_token": accessToken,
"refresh_token": refreshToken,
"client_id": clientID,
"grant_type": "refresh_token"
]
sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if
let json = response.result.value as? [String: Any],
let accessToken = json["access_token"] as? String,
let refreshToken = json["refresh_token"] as? String
{
completion(true, accessToken, refreshToken)
} else {
completion(false, nil, nil)
}
strongSelf.isRefreshing = false
}
}
}
let baseURLString = "https://some.domain-behind-oauth2.com"
let oauthHandler = OAuth2Handler(
clientID: "12345678",
baseURLString: baseURLString,
accessToken: "abcd1234",
refreshToken: "ef56789a"
)
let sessionManager = SessionManager()
sessionManager.adapter = oauthHandler
sessionManager.retrier = oauthHandler
let urlString = "\(baseURLString)/some/endpoint"
sessionManager.request(urlString).validate().responseJSON { response in
debugPrint(response)
}
将OAuth2Handler
作为SessionManager
的adapter
和retrier
后应用,它会通过自动刷新访问令牌并在相同顺序中重试所有失败请求来处理无效访问令牌错误。
如果您需要按创建顺序执行,可以通过它们的任务标识符进行排序。
上面的示例仅检查401
响应代码,这并不十分健壮,但它确实演示了如何检查无效访问令牌错误。在生产应用程序中,您可能需要检查realm
和很可能是《www-authenticate》头部的响应,虽然这取决于OAuth2的实现。
另一个重要的注意事项是,这个认证系统可以在多个会话管理器之间共享。例如,您可能需要为同一套Web服务同时使用default
和ephemeral
会话配置。上面的示例允许同一oauthHandler
实例在多个会话管理器之间共享,以管理单个刷新流程。
Alamofire为数据、字符串、JSON和属性列表提供了内置的响应序列化
Alamofire.request(...).responseData { (resp: DataResponse<Data>) in ... }
Alamofire.request(...).responseString { (resp: DataResponse<String>) in ... }
Alamofire.request(...).responseJSON { (resp: DataResponse<Any>) in ... }
Alamofire.request(...).responsePropertyList { resp: DataResponse<Any>) in ... }
这些响应包装了解序列化的值(Data、String、Any)或错误(网络、验证错误),以及元数据(URL请求、HTTP头、状态码、指标、...)。
您有多种方式来自定义所有这些响应元素
响应映射是实现自定义响应的最简单方法。它在保留最终错误和元数据的同时,转换响应值。例如,您可以将JSON响应 DataResponse<Any>
转换为包含应用程序模型(如 DataResponse<User>
)的响应。您使用 DataResponse.map
方法执行响应映射
Alamofire.request("https://example.com/users/mattt").responseJSON { (response: DataResponse<Any>) in
let userResponse = response.map { json in
// We assume an existing User(json: Any) initializer
return User(json: json)
}
// Process userResponse, of type DataResponse<User>:
if let user = userResponse.value {
print("User: { username: \(user.username), name: \(user.name) }")
}
}
当转换可能抛出错误时,使用 flatMap
Alamofire.request("https://example.com/users/mattt").responseJSON { response in
let userResponse = response.flatMap { json in
try User(json: json)
}
}
响应映射非常适合您的自定义完成处理程序
@discardableResult
func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
return Alamofire.request("https://example.com/users/mattt").responseJSON { response in
let userResponse = response.flatMap { json in
try User(json: json)
}
completionHandler(userResponse)
}
}
loadUser { response in
if let user = response.value {
print("User: { username: \(user.username), name: \(user.name) }")
}
}
当 map/flatMap 闭包可能处理大量数据时,请确保您在主线程之外执行它
@discardableResult
func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
let utilityQueue = DispatchQueue.global(qos: .utility)
return Alamofire.request("https://example.com/users/mattt").responseJSON(queue: utilityQueue) { response in
let userResponse = response.flatMap { json in
try User(json: json)
}
DispatchQueue.main.async {
completionHandler(userResponse)
}
}
}
map
和 flatMap
也可用于 下载响应。
在实现自定义响应序列化器或对象序列化方法之前,考虑如何处理可能发生的任何错误非常重要。有两种基本选项:以未修改的形式传递现有的错误,以在响应时间处理;或者,将所有错误包装在您的应用程序特定的 Error
类型中。
例如,这里有一个简单的 BackendError
枚举,它将在后面的示例中使用
enum BackendError: Error {
case network(error: Error) // Capture any underlying Error from the URLSession API
case dataSerialization(error: Error)
case jsonSerialization(error: Error)
case xmlSerialization(error: Error)
case objectSerialization(reason: String)
}
Alamofire 提供了用于字符串、JSON 和属性列表的内置响应序列化,但可以在 Alamofire.DataRequest
和 / 或 Alamofire.DownloadRequest
的扩展中添加其他序列化
例如,下面是如何使用 Ono 实现响应处理程序的方法
extension DataRequest {
static func xmlResponseSerializer() -> DataResponseSerializer<ONOXMLDocument> {
return DataResponseSerializer { request, response, data, error in
// Pass through any underlying URLSession error to the .network case.
guard error == nil else { return .failure(BackendError.network(error: error!)) }
// Use Alamofire's existing data serializer to extract the data, passing the error as nil, as it has
// already been handled.
let result = Request.serializeResponseData(response: response, data: data, error: nil)
guard case let .success(validData) = result else {
return .failure(BackendError.dataSerialization(error: result.error! as! AFError))
}
do {
let xml = try ONOXMLDocument(data: validData)
return .success(xml)
} catch {
return .failure(BackendError.xmlSerialization(error: error))
}
}
}
@discardableResult
func responseXMLDocument(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DataResponse<ONOXMLDocument>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.xmlResponseSerializer(),
completionHandler: completionHandler
)
}
}
可以使用泛型提供自动、类型安全的响应对象序列化。
protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: Any)
}
extension DataRequest {
func responseObject<T: ResponseObjectSerializable>(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DataResponse<T>) -> Void)
-> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else { return .failure(BackendError.network(error: error!)) }
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
struct User: ResponseObjectSerializable, CustomStringConvertible {
let username: String
let name: String
var description: String {
return "User: { username: \(username), name: \(name) }"
}
init?(response: HTTPURLResponse, representation: Any) {
guard
let username = response.url?.lastPathComponent,
let representation = representation as? [String: Any],
let name = representation["name"] as? String
else { return nil }
self.username = username
self.name = name
}
}
Alamofire.request("https://example.com/users/mattt").responseObject { (response: DataResponse<User>) in
debugPrint(response)
if let user = response.result.value {
print("User: { username: \(user.username), name: \(user.name) }")
}
}
相同的方法也可用于处理返回对象集合表示的端点。
protocol ResponseCollectionSerializable {
static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self]
}
extension ResponseCollectionSerializable where Self: ResponseObjectSerializable {
static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self] {
var collection: [Self] = []
if let representation = representation as? [[String: Any]] {
for itemRepresentation in representation {
if let item = Self(response: response, representation: itemRepresentation) {
collection.append(item)
}
}
}
return collection
}
}
extension DataRequest {
@discardableResult
func responseCollection<T: ResponseCollectionSerializable>(
queue: DispatchQueue? = nil,
completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self
{
let responseSerializer = DataResponseSerializer<[T]> { request, response, data, error in
guard error == nil else { return .failure(BackendError.network(error: error!)) }
let jsonSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response else {
let reason = "Response collection could not be serialized due to nil response."
return .failure(BackendError.objectSerialization(reason: reason))
}
return .success(T.collection(from: response, withRepresentation: jsonObject))
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
struct User: ResponseObjectSerializable, ResponseCollectionSerializable, CustomStringConvertible {
let username: String
let name: String
var description: String {
return "User: { username: \(username), name: \(name) }"
}
init?(response: HTTPURLResponse, representation: Any) {
guard
let username = response.url?.lastPathComponent,
let representation = representation as? [String: Any],
let name = representation["name"] as? String
else { return nil }
self.username = username
self.name = name
}
}
Alamofire.request("https://example.com/users").responseCollection { (response: DataResponse<[User]>) in
debugPrint(response)
if let users = response.result.value {
users.forEach { print("- \($0)") }
}
}
在与服务器和 Web 服务进行通信时使用安全的 HTTPS 连接是保护敏感数据的重要步骤。默认情况下,Alamofire 将使用由 Security 框架提供的 Apple 内置验证来评估服务器提供的证书链。虽然这保证了证书链有效,但它不能防止中间人攻击 (MITM) 或其他潜在漏洞。为了减轻中间人攻击,处理敏感客户数据或财务信息的应用程序应使用 ServerTrustPolicy
提供的证书或公钥固定。
ServerTrustPolicy
枚举评估在通过安全的 HTTPS 连接连接到服务器时 URLAuthenticationChallenge
提供的通用服务器信任。
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
有许多不同的服务器信任评估情况,这为您提供了完全控制验证过程的能力。
performDefaultEvaluation
:使用默认服务器信任评估,同时允许您控制是否验证挑战提供的替代主机。pinCertificates
:使用已固定证书来验证服务器信任。如果其中一个已固定证书与服务器证书之一匹配,则认为服务器信任是有效的。pinPublicKeys
:使用已固定公钥来验证服务器信任。如果其中一个已固定公钥与服务器证书公钥之一匹配,则认为服务器信任是有效的。disableEvaluation
:禁用所有评估,从而始终认为任何服务器信任都有效。customEvaluation
:使用相关闭包来评估服务器信任的有效性,从而为您完全控制验证过程。请谨慎使用。ServerTrustPolicyManager
负责将服务器信任策略存储到特定主机上的内部映射中。这使得 Alamofire 可以针对不同的服务器信任策略评估每个主机。
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .disableEvaluation
]
let sessionManager = SessionManager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
务必要保存对新
SessionManager
实例的引用,否则当您的sessionManager
被释放时,您的所有请求都将被取消。
这些服务器信任策略将导致以下行为
test.example.com
将始终使用证书固定,并开启了证书链和主机验证,因此需要满足以下条件才能使 TLS 握手成功insecure.expired-apis.com
永远不会评估证书链,并且始终允许 TLS 握手成功。如果您发现自己需要更灵活的服务器信任策略匹配行为(例如通配符域名),则可以子类化 ServerTrustPolicyManager
并用您自己的自定义实现覆盖 serverTrustPolicyForHost
方法。
class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
var policy: ServerTrustPolicy?
// Implement your custom domain matching behavior...
return policy
}
}
所有 .performDefaultEvaluation
、.pinCertificates
和 .pinPublicKeys
服务器信任策略都接受一个 validateHost
参数。将该值设置为 true
将导致服务器信任评估验证证书中的主机名与挑战的主机名匹配。如果它们不匹配,评估将失败。一个 validateHost
值为 false
仍然将评估整个证书链,但不会验证叶证书的主机名。
建议在生产环境中始终将
validateHost
设置为true
。
固定证书和公钥都有使用 validateCertificateChain
参数验证证书链的选项。通过将该值设置为 true
,除了执行与固定证书或公钥的字节相等性检查外,还将评估完整的证书链。一个值为 false
将跳过证书链验证,但仍然执行字节相等性检查。
在某些情况下,可能需要禁用证书链验证。禁用验证最常见的情况是自签名证书和过期证书。在这两种情况下,评估总是会失败,但字节相等性检查仍将确保您从服务器接收到预期的证书。
建议在生产环境中始终将
validateCertificateChain
设置为true
。
随着在 iOS 9 中添加了应用传输安全(ATS),使用具有几个 ServerTrustPolicy
对象的自定义 ServerTrustPolicyManager
可能没有效果。如果你持续看到 CFNetwork SSLHandshake failed (-9806)
错误,那么你可能遇到了这个问题。Apple 的 ATS 系统会覆盖整个挑战系统,除非您在上传应用的信息.plist中配置 ATS 设置来禁用足够的配置,以允许您的应用评估服务器信任。
如果您遇到这个问题(自签名证书的情况下可能概率很高),您可以通过以下方式来解决。
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<!-- Optional: Specify minimum TLS version -->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>
</dict>
是否需要将 NSExceptionRequiresForwardSecrecy
设置为 NO
取决于您的 TLS 连接是否使用了允许的密码套件。在某些情况下,需要将其设置为 NO
。为了允许 SessionDelegate
接收挑战回调,必须将 NSExceptionAllowsInsecureHTTPLoads
设置为 YES
。一旦挑战回调被调用,ServerTrustPolicyManager
将接管服务器信任评估。您可能还需要指定 NSTemporaryExceptionMinimumTLSVersion
,如果您尝试连接仅支持 TLS 版本小于 1.2
的主机。
建议在生产环境中始终使用有效的证书。
NetworkReachabilityManager
监听 WWAN 和 WiFi 网络接口的主机和地址的可达性变化。
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.listener = { status in
print("Network Status Changed: \(status)")
}
manager?.startListening()
请确保在上面的例子中保留
manager
,否则不会有状态变化报告。
此外,不要在host
字符串中包含方案,否则可达性可能无法正常工作。
在使用网络可达性确定下一步操作时,有一些重要的事情需要记住。
建议查看 WWDC 2012 Session 706,“网络最佳实践” 以获取更多信息。
以下雷达对 Alamofire 当前的实现有一些影响。
rdar://21349340
- 测试案例中的通行桥接问题导致编译器抛出警告rdar://26761490
- Swift 字符串插值在常见用法中导致内存泄漏rdar://26870455
- 在模拟器中背景 URL 会话配置不起作用rdar://26849668
- 一些 URLProtocol API 未正确处理 URLRequest
Alamofire 以 Alamo Fire flower 命名,这是一种蓝CRET骨花的混合变种,是德克萨斯州的州花。
简单、静态数据,如路径、参数和常用标头属于 Router
。动态数据,如可能基于身份验证系统更改值的 Authorization
标头,属于 RequestAdapter
。
动态数据之所以必须放在 RequestAdapter
中,是为了支持重试操作。当 Request
被重试时,原始请求不会被重建,这意味着不会再次调用 Router
。然后再次调用 RequestAdapter
允许在重试请求之前在原始请求上更新动态数据。
Alamofire 由 Alamofire 软件基金会 所有并提供维护。您可以通过 Twitter @AlamofireSF 关注其项目更新和发布。
如果您认为您已识别出针对 Alamofire 的安全漏洞,请尽快通过电子邮件报告给它 [email protected]。请勿将其发布到公共问题跟踪器。
ASF 正在筹集资金以正式注册为联邦非营利组织。注册将使我们成员获得一些法律保护,并允许我们免税使用捐款。向 ASF 捐款将使我们能够
社区对ASF库的采用令人惊叹。我们对您对这些项目的热情感到非常谦卑,并希望能够继续尽我们所能推动项目进展。在您的持续支持下,ASF将能够扩大其影响范围,并为核心成员提供更好的法律保障。如果您在工作中使用我们的任何库,看看您的雇主是否有兴趣捐赠。我们的初步目标是筹集1000美元,以便我们的法律事务一切顺利,并启动这项运动。您今天能捐赠的任何金额都对我们实现目标至关重要。
Alamofire采用MIT许可发布。《查看许可证》以获取详细信息。