原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、系统提供的原生框架URLSession的使用
- 1、请求网络的流程
- 2、属性
- 3、HTTP
- 二、初涉Alamofire
- 1、发起请求
- 2、HTTP Methods
- 3、请求参数和参数编码器
- 4、HTTP Headers
- 5、响应验证
- 6、响应处理
- 7、身份验证
- 8、下载文件
- 9、上传数据到服务器
- 10、网络可达性
- 三、玩转Alamofire
- 1、Session
- 2、Request
- 3、Security
- Demo
- 参考文献
一、系统提供的原生框架URLSession的使用
1、请求网络的流程
a、请求网络的基本方式
-
URLSession.shared
提供了一个共享的单例会话对象,它为创建任务提供了一个默认行为。使用共享会话仅用几行代码就可以将URL
的内容获取到。 -
dataTask
创建一个网络会话数据任务。 - 网络任务默认是挂起的,调用
resume
开始进行连接请求网络 - 请求成功或者失败都会返回结果闭包,其实闭包只是一层封装,真正来的是
URLSession
的代理 - 在下面的过程中,我们省略了一个重要的东西:
URLSessionConfiguration
let url = URL(string: "https://www.baidu.com")!
URLSession.shared.dataTask(with: url)
{ (data, response, error) in
if error == nil
{
print("请求网络成功:\(String(describing: response))" )
}
}.resume()
输出结果为:
请求网络成功:Optional( { URL: https://www.baidu.com/ } { Status Code: 200, Headers {
"Content-Encoding" = (
gzip
);
"Content-Length" = (
1145
);
"Content-Type" = (
"text/html"
);
Date = (
"Thu, 21 Jan 2021 07:15:16 GMT"
);
Server = (
bfe
);
} })
b、区别Configuration中的default与ephemeral
- default:默认模式,通常我们用这种模式就足够了。
default
模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书 - ephemeral:系统没有进行任何持久性存储,所有内容的生命周期都与
session
相同,当session
无效时,所有内容自动释放
let defaultConfiguration = URLSessionConfiguration.default
let ephemeralConfiguration = URLSessionConfiguration.ephemeral
print("default 沙盒大小: \(String(describing: defaultConfiguration.urlCache?.diskCapacity))")
print("default 内存大小: \(String(describing: defaultConfiguration.urlCache?.memoryCapacity))")
print("ephemeral 沙盒大小: \(String(describing: ephemeralConfiguration.urlCache?.diskCapacity))")
print("ephemeral 内存大小: \(String(describing: ephemeralConfiguration.urlCache?.memoryCapacity))")
从输出结果中可以看到ephemeral
的沙盒大小为0,而default
有一个沙盒大小,即系统为default
提供了一定大小的沙盒来存储证书等内容。
default 沙盒大小: Optional(10000000)
default 内存大小: Optional(512000)
ephemeral 沙盒大小: Optional(0)
ephemeral 内存大小: Optional(512000)
c、切换到后台停止下载问题
-
background
会创建一个可以在后台甚至APP已经关闭的时候仍然传输数据的会话 -
background
模式与default
模式非常相似,不过background
模式会用一个独立线程来进行数据传输 -
background
模式可以在程序挂起,退出,崩溃的情况下,在重新启动APP时继续运行task
- 可以利用标识符来进行恢复
task
。后台Session
一定要在创建的时候赋予一个唯一的identifier
,这样在APP下次运行的时候,能够根据identifier
来进行相关的区分 - 如果用户强制关闭了APP,IOS 系统会关闭所有的
background Session
,只有当用户下次启动了APP,数据传输任务才会继续执行
// 配置Configuration
let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: createID())
// 创建URLSession
let backgroundURLSession = URLSession.init(configuration: backgroundConfiguration, delegate: self, delegateQueue: OperationQueue.main)
// 开始下载
backgroundURLSession.downloadTask(with: downloadUrl).resume()
❶ 下载完成后进行沙盒迁移,拷贝下载完成的文件到用户目录(文件名以时间戳命名)
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
{
print("下载完成后文件位置:\(location)")
let originalLocationPath = location.path
let destinationPath = NSHomeDirectory() + "/Documents/" + currentDateTurnString() + ".mp4"
print("文件移动后的位置:\(destinationPath)")
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: originalLocationPath, toPath: destinationPath)
}
输出结果为:
下载完成后文件位置:file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Library/Caches/com.apple.nsurlsessiond/Downloads/com.xiejiapei.UseAlamofire/CFNetworkDownload_LX9nZT.tmp
文件移动后的位置:/Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/67D3E5B6-989E-4137-9ED0-06F852F31CA3/Documents/20210121145809.mp4
❷ 计算下载进度
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
print("bytesWritten: \(bytesWritten)\n totalBytesWritten: \(totalBytesWritten)\n totalBytesExpectedToWrite: \(totalBytesExpectedToWrite)")
print("下载进度条:\( Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) )")
}
输出结果为:
......
bytesWritten: 139246
totalBytesWritten: 17218551
totalBytesExpectedToWrite: 17244422
下载进度条:0.998499746758691
bytesWritten: 25871
totalBytesWritten: 17244422
totalBytesExpectedToWrite: 17244422
下载进度条:1.0
❸ 保存后台下载时的completionHandler,用于开启后台下载权限
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void)
{
self.backgroundSessionCompletionHandler = completionHandler
}
如果不这样做,当用户将APP切换到后台的时候下载就会直接中断,但是当回到APP的时候会继续之前的进度进行下载。
2021-01-21 15:34:12.825608+0800 UseAlamofire[53235:1879599] BackgroundSession <01EC4B4A-A81E-4D5B-9004-CF615DEDFD87> connection to background transfer daemon interrupted
❹ 调用保存的后台下载回调,告诉系统及时更新屏幕
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession)
{
print("让后台任务保持下载")
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
backgroundHandle()
}
}
输出结果为:
让后台任务保持下载
需要注意的是,当我们切换到后台的时候,任务虽然还在下载,但是下载进度将不会再打印到控制台上,你不要以为下载终止了(,我起初就是这样以为的,还查了些资料)。
2、属性
常规属性
- identifier:配置对象的后台会话标识符
- httpAdditionalHeaders:与请求一起发送的附加头文件的字典
- networkServiceType:网络服务的类型
- allowsCellularAccess:一个布尔值,用于确定是否应通过蜂窝网络进行连接
- timeoutIntervalForRequest:等待其他数据时使用的超时间隔
- timeoutIntervalForResource:资源请求应该允许的最大时间量
- sharedContainerIdentifier:应该下载后台URL会话中的文件的共享容器的标识符
- waitsForConnectivity:一个布尔值,指示会话是否应等待连接变为可用或者立即失败
设置Cookie政策
- httpCookieAcceptPolicy:决定何时应该接受
Cookie
的策略常量 - httpShouldSetCookies:一个布尔值,用于确定请求是否应包含来自
Cookie
存储的内容 - httpCookieStorage:管理
cookie
存储的单一对象(共享实例) - HTTPCookie:表示
HTTP cookie
的对象。它是一个不可变的对象,从包含cookie
属性的字典中初始化
设置安全策略
- tlsMaximumSupportedProtocol:在此会话中进行连接时客户端应请求的最大
TLS
协议版本 - tlsMinimumSupportedProtocol:协议协商期间应该接受的最小
TLS
协议 - urlCredentialStorage:提供身份验证凭据的凭证存储
设置缓存策略
- urlCache:用于向会话中的请求提供缓存响应的
URL
缓存 - requestCachePolicy:一个预定义常量,用于确定何时从缓存中返回响应
支持后台转移
- sessionSendsLaunchEvents:一个布尔值,指示在传输完成时是否应该在后台继续或启动应用程序
- isDiscretionary:一个布尔值,用于确定是否可以根据系统的判断来调度后台任务以获得最佳性能
支持自定义协议
- protocolClasses:在会话中处理请求的额外协议子类的数组
- URLProtocol:一个
NSURLProtocol
对象处理加载协议特定的URL
数据。NSURLProtocol
类本身是一个抽象类,可以为与特定URL
方案的URL
处理基础设施。您可以为您的应用支持的任何自定义协议或URL
方案创建子类
支持多路径TCP
- multipathServiceType:指定用于通过Wi-Fi和蜂窝接口传输数据的多路径
TCP
连接策略的服务类型 - URLSessionConfiguration.MultipathServiceType:指定多路径
TCP
使用的服务类型的常量
设置HTTP策略和代理属性
- httpMaximumConnectionsPerHost:同时连接到给定主机的最大数量
- httpShouldUsePipelining:一个布尔值,用于确定会话是否应使用HTTP流水线
- connectionProxyDictionary:包含有关在此会话中使用的代理信息的字典
支持连接变化
- waitsForConnectivity:一个布尔值,指示会话是否应等待连接变为可用或者立即失败
默认缓存策略
- NSURLRequestUseProtocolCachePolicy = 0:如果请求拥有一个缓存的响应,那么
URL
加载系统会检查这个响应来决定。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取。
3、HTTP
a、三次握手
为什么不是两次握手?
没有第三次握手,服务端就不知道客户端是否可以接收到消息,即确定服务端和客户端两者都可以发送消息也可以接收消息。
为什么不是四次握手?
通过三次握手已经确定服务端和客户端两者都可以发送消息也可以接收消息了,没必要再进行第四次握手。
b、四次挥手
为什么不是两次挥手?
因为服务器需要通过第三次挥手将还没有传输完成的数据全部传输完成。
为什么不是三次挥手?
因为服务器需要通过第三次挥手将还没有传输完成的数据全部传输完成,而客户端需要通过第四次挥手告诉服务端第三次挥手发送过来的数据已经全部接收完毕,通过四次挥手可以保证整个通讯过程的完整性。
c、使用Wireshark工具抓包
❶ 两个终端之间达到握手
当在下图左侧的终端输入的时候会自动显示到右侧终端进行同步,即两个终端之间成功达到了握手。
❷ 进入Loopback界面
❸ 在终端重新进行握手让Loopback开始监听
❹ 监听到三次握手流程中Seq和Ack的变化
❺ 在终端断掉握手让Loopback监听四次挥手
❺ 监听到四次挥手流程中Seq和Ack的变化
d、OSL七层协议
- 应用层(HTTP、FTP、DNS):文件传输,电子邮件,文件服务,虚拟终端
- 表示层(没有协议):数据格式化,代码转换,数据加密
- 会话层(没有协议):解除或建立与别的接点的联系
- 传输层(TCP、UDP):提供端对端的接口
- 网络层(IP):为数据包选择路由
- 数据链路层(SLIP):传输有地址的帧以及错误检测功能
- 物理层(ISO2110):以二进制数据形式在物理媒体上传输数据
七层协议
包装与解包
二、初涉Alamofire
Alamofire
为 HTTP
网络请求提供了一个优雅且可组合的接口。它没有实现自己的 HTTP
网络功能。取而代之的是,它建立在由Foundation
框架提供的URL
加载系统之上。系统的核心是 URLSession
和 URLSessionTask
子类。
1、发起请求
Alamofire
为发出 HTTP
请求提供了多种方便的方法。
a、最简单的请求方式只需提供一个可以转换为 URL 的 String
- 链式语法
- 返回
JSON
- 直接放入
String
,而不是URL
AF.request("https://httpbin.org/get").response
{ response in
debugPrint(response)
}
输出结果为:
[Request]: GET https://httpbin.org/get
[Headers]: None
[Body]: None
[Response]:
[Status Code]: 200
[Headers]:
access-control-allow-credentials: true
Access-Control-Allow-Origin: *
Content-Length: 426
Content-Type: application/json
Date: Mon, 25 Jan 2021 05:35:54 GMT
Server: gunicorn/19.9.0
[Body]:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
"Accept-Language": "en;q=1.0",
"Host": "httpbin.org",
"User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1",
"X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
},
"origin": "222.76.251.163",
"url": "https://httpbin.org/get"
}
[Network Duration]: 1.434872031211853s
[Serialization Duration]: 0.0s
[Result]: success(Optional(426 bytes))
这实际上是一种缩写形式,它的完整定义如下。此方法创建一个 DataRequest
,允许传入多个参数。
open func request(
_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil
) -> DataRequest
b、为遵循 Alamofire 的 URLRequestConvertible 协议的任何类型创建 DataRequest
open func request(
_ urlRequest: URLRequestConvertible,
interceptor: RequestInterceptor? = nil
) -> DataRequest
2、HTTP Methods
a、作为 method 参数传递给 AF.request API
不同的 HTTP
方法可能有不同的语义,需要不同的参数编码,这取决于服务器的期望。例如,URLSession
或 Alamofire
不支持在 GET
请求中传递 body
数据,否则将返回错误。
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
}
}
这些值可以作为 method
参数传递给 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)
b、Alamofire 还提供了对 URLRequest 的扩展
以桥接将字符串返回到 HTTPMethod
值的 httpMethod
属性。
extension URLRequest
{
/// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.
public var method: HTTPMethod?
{
get { httpMethod.flatMap(HTTPMethod.init) }
set { httpMethod = newValue?.rawValue }
}
}
c、扩展HTTPMethod类型添加自定义值
如果需要使用 Alamofire
的 HTTPMethod
类型不支持的 HTTP
方法,可以扩展该类型以添加自定义值。
extension HTTPMethod
{
static let custom = HTTPMethod(rawValue: "CUSTOM")
}
3、请求参数和参数编码器
a、请求参数
Alamofire
支持将遵守 Encodable
协议的类型作为请求参数。这些请求参数通过遵循 ParameterEncoder
协议的参数编码器进行传递并添加到 URLRequest
中,最后通过网络发送。Alamofire
包含两种遵循 ParameterEncoder
协议的参数编码器:JSONParameterEncoder
和 URLEncodedFormParameterEncoder
。这两种参数编码器涵盖了最常见的编码方式。
struct Login: Encodable
{
let email: String
let password: String
}
let login = Login(email: "[email protected]", password: "19970118")
AF.request("https://httpbin.org/post", method: .post, parameters: login, encoder: JSONParameterEncoder.default).response
{ response in
debugPrint(response)
}
输出结果为:
[Request]: POST https://httpbin.org/post
[Headers]:
Content-Type: application/json
[Body]:
{"email":"[email protected]","password":"19970118"}
[Response]:
[Status Code]: 200
[Headers]:
access-control-allow-credentials: true
Access-Control-Allow-Origin: *
Content-Length: 682
Content-Type: application/json
Date: Mon, 25 Jan 2021 06:01:51 GMT
Server: gunicorn/19.9.0
[Body]:
{
"args": {},
"data": "{\"email\":\"[email protected]\",\"password\":\"19970118\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
"Accept-Language": "en;q=1.0",
"Content-Length": "51",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1",
"X-Amzn-Trace-Id": "Root=1-600e5ecf-01e2ada305b5bd8a0e0e0dc6"
},
"json": {
"email": "[email protected]",
"password": "19970118"
},
"origin": "222.76.251.163",
"url": "https://httpbin.org/post"
}
[Network Duration]: 2.090991973876953s
[Serialization Duration]: 0.0s
[Result]: success(Optional(682 bytes))
b、使用 URL 编码参数的 GET 请求(默认编码方式)
// https://httpbin.org/get?foo=bar
let parameters = ["foo": "bar"]
// 下面三种方法都是等价的
AF.request("https://httpbin.org/get", parameters: parameters)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)
AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))
输出结果为:
[Request]: GET https://httpbin.org/get?foo=bar
c、使用 URL 编码参数的 POST 请求
// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"
let parameters: [String: [String]] =
[
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
// 下面三种方法都是等价的
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))
输出结果为:
[Request]: POST https://httpbin.org/post
[Headers]:
Content-Type: application/x-www-form-urlencoded; charset=utf-8
[Body]: 73 bytes
"form": {
"baz[]": [
"a",
"b"
],
"foo[]": "bar",
"qux[]": [
"x",
"y",
"z"
]
},
d、JSON 编码参数的 POST 请求
// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}
let parameters: [String: [String]] =
[
"foo": ["bar"],
"baz": ["a", "b"],
"qux": ["x", "y", "z"]
]
AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: Alamofire.JSONParameterEncoder.default).response
{ response in
debugPrint(response)
}
输出结果为:
[Request]: POST https://httpbin.org/post
[Headers]:
Content-Type: application/json
[Body]:
{"foo":["bar"],"qux":["x","y","z"],"baz":["a","b"]}a
4、HTTP Headers
a、构造HTTP Headers
Alamofire
包含自己的 HTTPHeaders
类型,这是一种保持顺序且不区分大小写的 name
/value
对的表示。HTTPHeader
类型可以封装单个 name
/value
对,并为常用的 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
类型也可以采用如下方式进行构造:
let anotherHeaders: HTTPHeaders =
[
.authorization(username: "Username", password: "Password"),
.accept("application/json")
]
b、Session 为每个 Request 提供一组默认的 headers
- Accept-Encoding:默认为
"br;q=1.0, gzip;q=0.9, deflate;q=0.8"
- Accept-Language:默认系统中最多含有 6 种首选语言,格式为
"en;q=1.0"
- User-Agent:包含有关应用程序的版本信息,例如
"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1"
5、响应验证
默认情况下,无论响应的内容如何,Alamofire
都会将任何已完成的请求视为成功。如果响应具有不可接受的状态代码或 MIME
类型,则在响应处理程序之前调用 validate()
将导致生成错误。
a、自动验证
validate()
会自动验证状态代码是否在200..<300
范围内,以及响应的Content-Type header
是否与请求的 Accept
匹配(如果有提供)。
AF.request("https://httpbin.org/get").validate().responseJSON
{ response in
debugPrint(response)
}
输出结果为:
[Response]:
[Status Code]: 200
[Headers]:
access-control-allow-credentials: true
Access-Control-Allow-Origin: *
Content-Length: 427
Content-Type: application/json
Date: Mon, 25 Jan 2021 07:38:36 GMT
Server: gunicorn/19.9.0
b、手动验证
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)
}
}
输出结果为:
Validation Successful
6、响应处理
不管被序列化成哪种类型,结果都会通过闭包的参数response
返回,如果是被序列化的数据,就通过resonse
中的result.value
来获取数据。源码中response
闭包函数的返回值是Self
,也就是Request
,这就让我们能够使用链式访问来做一些很有意思的事情,任务按照顺序依次放入到队列中。
a、Handler
不计算任何响应数据。它只是直接从 URLSessionDelegate
转发所有信息。
// 未序列化的 Response
func response(
queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
// 序列化的 Response
func response(
queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
使用方式为:
AF.request("https://httpbin.org/get").response
{ response in
debugPrint("Response: \(response)")
}
输出结果为:
"Response: success(Optional(426 bytes))"
b、Data Handler
使用 DataResponseSerializer
提取并验证服务器返回的数据。如果没有发生错误并且返回数据,则响应结果将为 .success
, value
将为从服务器返回的 Data
。
func responseData(
queue: DispatchQueue = .main,
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
使用方式为:
AF.request("https://httpbin.org/get").responseData
{ response in
debugPrint("Response: \(response)")
}
输出结果为:
"Response: success(427 bytes)"
c、String Handler
使用 StringResponseSerializer
将服务器返回的数据转换为具有指定编码的String
。如果没有发生错误,并且服务器数据成功序列化为 String
,则响应结果将为 .success
,并且值的类型为 String
。
func responseString(
queue: DispatchQueue = .main,
encoding: String.Encoding? = nil,
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
使用方式为:
AF.request("https://httpbin.org/get").responseString
{ response in
debugPrint("Response: \(response)")
}
输出结果为:
"Response: success(\"{\\n \\\"args\\\": {}, \\n \\\"headers\\\": {\\n \\\"Accept\\\": \\\"*/*\\\", \\n \\\"Accept-Encoding\\\": \\\"br;q=1.0, gzip;q=0.9, deflate;q=0.8\\\", \\n \\\"Accept-Language\\\": \\\"en;q=1.0\\\", \\n \\\"Host\\\": \\\"httpbin.org\\\", \\n \\\"User-Agent\\\": \\\"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\\\", \\n \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-600e8ac6-70026c993647252b60805135\\\"\\n }, \\n \\\"origin\\\": \\\"218.104.139.115\\\", \\n \\\"url\\\": \\\"https://httpbin.org/get\\\"\\n}\\n\")"
d、JSON Handler
使用指定的 JSONSerialization.ReadingOptions
将服务器返回的数据转换为 Any
类型。如果没有出现错误,并且服务器数据成功序列化为 JSON
对象,则响应 AFResult
将为 .success
,值将为 Any
类型。
func responseJSON(
queue: DispatchQueue = .main,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
使用方式为:
AF.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint("Response: \(response)")
}
输出结果为:
"Response: success({\n args = {\n };\n headers = {\n Accept = \"*/*\";\n \"Accept-Encoding\" = \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\";\n \"Accept-Language\" = \"en;q=1.0\";\n Host = \"httpbin.org\";\n \"User-Agent\" = \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\";\n \"X-Amzn-Trace-Id\" = \"Root=1-600e8b4a-28fe0a1064c7a6e1033414fa\";\n };\n origin = \"218.104.139.115\";\n url = \"https://httpbin.org/get\";\n})"
e、可解码类型的Handler
使用 DecodableResponseSerializer
和 指定的 DataDecoder
(Decoder
的协议抽象,可以从 Data
解码)将服务器返回的数据转换为传递进来的 Decodable
类型。如果没有发生错误,并且服务器数据已成功解码为 Decodable
类型,则响应 Result
将为 .success
,并且 value
将为传递进来的类型。
func responseDecodable(
of type: T.Type = T.self,
queue: DispatchQueue = .main,
decoder: DataDecoder = JSONDecoder(),
completionHandler: @escaping (AFDataResponse) -> Void
) -> Self
使用方式为:
struct HTTPBinResponse: Decodable
{
let url: String
}
AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self)
{ response in
debugPrint("Response: \(response)")
}
输出结果为:
"Response: success(UseAlamofire.HTTPBinResponse(url: \"https://httpbin.org/get\"))"
f、链式响应
没有一个响应 handlers
对从服务器返回的 HTTPURLResponse
执行任何验证。例如,400..<500
和 500..<600
范围内的响应状态代码不会自动触发错误。Alamofire
使用链式的响应验证来实现这一点。对同一请求使用多个响应 handlers
需要多次序列化服务器数据,每个响应 handlers
均处理一次。通常应避免对同一请求使用多个响应 handlers
,特别是在生产环境中。
AF.request("https://httpbin.org/get")
.responseString
{ response in
print("Response String: \(String(describing: response.value) )")
}
.responseJSON
{ response in
print("Response JSON: \(String(describing: response.value))")
}
输出结果为:
Response String: Optional("{\n \"args\": {}, \n \"headers\": {\n \"Accept\": \"*/*\", \n \"Accept-Encoding\": \"br;q=1.0, gzip;q=0.9, deflate;q=0.8\", \n \"Accept-Language\": \"en;q=1.0\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1\", \n \"X-Amzn-Trace-Id\": \"Root=1-600e8e25-66ed7b9b0986f02971550391\"\n }, \n \"origin\": \"218.104.139.115\", \n \"url\": \"https://httpbin.org/get\"\n}\n")
Response JSON: Optional({
args = {
};
headers = {
Accept = "*/*";
"Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
"Accept-Language" = "en;q=1.0";
Host = "httpbin.org";
"User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
"X-Amzn-Trace-Id" = "Root=1-600e8e25-66ed7b9b0986f02971550391";
};
origin = "218.104.139.115";
url = "https://httpbin.org/get";
})
g、响应队列
默认情况下,传递给响应 handler
的闭包在 .main
队列上执行,但可以传递一个指定的 DispatchQueue
来执行闭包。实际的序列化工作(将 Data
转换为其他类型)总是在后台队列上执行。
let utilityQueue = DispatchQueue.global(qos: .utility)
AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue)
{ response in
print("在全局队列上执行此网络请求:\(Thread.current)")
debugPrint(response)
}
输出结果为:
在全局队列上执行此网络请求:{number = 8, name = (null)}
7、身份验证
a、自动提供 URLCredential
Request
的 authenticate
方法将在使用 URLAuthenticationChallenge
进行质询时自动提供 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)
}
输出结果为:
[Response]:
[Status Code]: 200
[Headers]:
access-control-allow-credentials: true
Access-Control-Allow-Origin: *
Content-Length: 47
Content-Type: application/json
Date: Mon, 25 Jan 2021 10:01:37 GMT
Server: gunicorn/19.9.0
[Body]:
{
"authenticated": true,
"user": "user"
}
b、自己提供 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)
}
输出结果同上。
8、下载文件
a、下载数据到文件中
除了将数据提取到内存中之外,Alamofire
还提供了 Session.download
、DownloadRequest
和 DownloadResponse
以方便下载数据到磁盘。虽然下载到内存中对小负载(如大多数 JSON API
响应)非常有用,但获取更大的资源(如图像和视频)应下载到磁盘,以避免应用程序出现内存问题。DownloadRequest
具有与 DataRequest
相同的大多数响应 handlers
。但是,由于它将数据下载到磁盘,因此序列化响应涉及从磁盘读取,还可能涉及将大量数据读入内存。在设计下载处理时,记住这些事实是很重要的。
AF.download("https://httpbin.org/image/png").responseData
{ response in
if let data = response.value
{
let image = UIImage(data: data)
self.imageView.image = image!
}
}
b、下载文件的存放位置
所有下载的数据最初都存储在系统临时目录中。它最终会在将来的某个时候被系统删除,所以如果它需要更长的寿命,将文件移到其他地方是很重要的。我们可以提供 Destination
闭包,将文件从临时目录移动到最终的存放位置。在临时文件实际移动到 destinationURL
之前,将执行闭包中指定的 Options
。当前支持的两个 Options
是:.createIntermediateDirectories
如果指定,则为目标 URL
创建中间目录。.removePreviousFile
如果指定,则从目标 URL
中删除以前的文件。
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)
self.imageView.image = image!
}
}
输出结果为:
[Request]: GET https://httpbin.org/image/png
[Headers]: None
[Body]: None
[Response]:
[Status Code]: 200
[Headers]:
access-control-allow-credentials: true
Access-Control-Allow-Origin: *
Content-Length: 8090
Content-Type: image/png
Date: Mon, 25 Jan 2021 10:14:50 GMT
Server: gunicorn/19.9.0
[File URL]: /Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png
[Resume Data]: None
[Network Duration]: 1.8333059549331665s
[Serialization Duration]: 0.0s
[Result]: success(Optional(file:///Users/xiejiapei/Library/Developer/CoreSimulator/Devices/9AF7A16E-76FF-4711-8BFA-66DDF38D03F2/data/Containers/Data/Application/730436F5-DE88-42CA-96AF-B5FC5A4C9019/Documents/image.png))
文件存储的位置为
还可以使用建议的文件存储位置,效果同上。
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
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)
self.imageView.image = image!
}
}
c、下载进度
任何 DownloadRequest
都可以使用 downloadProgress
报告下载进度。只有在服务器正确返回可用于计算进度的 Content-Length header
时才能工作。如果没有这个 header
,进度将保持在 0.0,直到下载完成,此时进度将跳到 1.0。还可以接收一个queue
参数,该参数定义应该对哪个 DispatchQueue
调用下载进度闭包。
let utilityQueue = DispatchQueue.global(qos: .utility)
AF.download("https://httpbin.org/image/png")
.downloadProgress(queue: utilityQueue)
{ progress in
print("下载进度: \(progress.fractionCompleted)")
}
.responseData
{ response in
if let data = response.value
{
let image = UIImage(data: data)
self.imageView.image = image!
}
}
输出结果为:
下载进度: 1.0
d、取消和恢复下载
除了所有请求类都有 cancel()
方法外,DownloadRequest
还可以生成恢复数据,这些数据可以用于以后恢复下载。此 API 有两种形式:1)cancel(producingResumeData: Bool)
,它允许控制是否生成恢复数据,但仅在 DownloadResponse
可用;2)cancel(byProducingResumeData: (_ resumeData: Data?) -> Void)
,它执行相同的操作,但恢复数据在 completion handler
中可用。如果DownloadRequest
被取消或中断,则底层的 URLSessionDownloadTask
可能会生成恢复数据。如果发生这种情况,可以重新使用恢复数据来重新启动停止的 DownloadRequest
。
let download = AF.download("https://httpbin.org/image/png")
var resumeData: Data!
// 正常下载
download.responseData
{ response in
if let data = response.value
{
let image = UIImage(data: data)
self.imageView.image = image!
}
}
// 从cancel的回调闭包中获得resumeData
download.cancel
{ data in
resumeData = data
}
// 使用resumeData继续下载
AF.download(resumingWith: resumeData).responseData
{ response in
if let data = response.value
{
let image = UIImage(data: data)
self.imageView.image = image!
}
}
9、上传数据到服务器
当使用 JSON
或 URL
编码的参数向服务器发送相对少量的数据时,request()
通常就足够了。如果需要从内存、文件 URL
或 InputStream
中的 Data
发送大量数据,那么 upload()
就是您想要使用的。
a、上传 Data
let data = Data("XieJiaPei".utf8)
AF.upload(data, to: "https://httpbin.org/post").responseJSON
{ response in
debugPrint(response)
}
输出结果为:
[Result]: success({
args = {
};
data = "";
files = {
};
form = {
XieJiaPei = "";
};
headers = {
Accept = "*/*";
"Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
"Accept-Language" = "en;q=1.0";
"Content-Length" = 9;
"Content-Type" = "application/x-www-form-urlencoded";
Host = "httpbin.org";
"User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
"X-Amzn-Trace-Id" = "Root=1-600f6966-48de0e395d96f17f396e2b67";
};
json = "";
origin = "222.76.251.163";
url = "https://httpbin.org/post";
})
b、上传多表单数据
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(Data("Boy".utf8), withName: "JiaPei")
multipartFormData.append(Data("Girl".utf8), withName: "YuQing")
}, to: "https://httpbin.org/post")
.responseJSON { response in
debugPrint(response)
}
输出结果为:
[Result]: success({
args = {
};
data = "";
files = {
};
form = {
JiaPei = Boy;
YuQing = Girl;
};
headers = {
Accept = "*/*";
"Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
"Accept-Language" = "en;q=1.0";
"Content-Length" = 228;
"Content-Type" = "multipart/form-data; boundary=alamofire.boundary.6d21176fdb63050f";
Host = "httpbin.org";
"User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
"X-Amzn-Trace-Id" = "Root=1-600f6d84-25a167ab5ddb02c206ff4697";
};
json = "";
origin = "218.104.139.115";
url = "https://httpbin.org/post";
})
c、上传文件
let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!
AF.upload(fileURL, to: "https://httpbin.org/post").responseJSON
{ response in
debugPrint(response)
}
输出结果为:
无
d、上传进度
当用户等待上传完成时,有时向用户显示上传的进度会很方便。任何 UploadRequest
都可以使用 uploadProgress
和 downloadProgress
报告响应数据下载的上传进度和下载进度。
let fileURL = Bundle.main.url(forResource: "girl", withExtension: "mp4")!
if FileManager.default.fileExists(atPath: fileURL.path)
{
AF.upload(fileURL, to: "https://httpbin.org/post")
.uploadProgress
{ progress in
print("上传进度: \(progress.fractionCompleted)")
}
.responseJSON
{ response in
print("上传完成")
print(response)
}
}
else
{
print("没有找到文件")
}
输出结果为:
上传进度: 0.01685915418681258
上传进度: 0.5394929339780026
上传进度: 0.6743661674725031
上传进度: 0.9441126344615044
上传进度: 1.0
10、网络可达性
监听移动网络和 WiFi 网络接口的主机和地址的可达性变化。
let manager = NetworkReachabilityManager(host: "www.apple.com")
manager?.startListening
{ status in
print("网络状态发生改变: \(status)")
}
输出结果为:
网络状态发生改变: reachable(Alamofire.NetworkReachabilityManager.NetworkReachabilityStatus.ConnectionType.ethernetOrWiFi)
三、玩转Alamofire
1、Session
a、Session.default
Alamofire
的 Session
在职责上大致等同于它维护的 URLSession
实例:它提供 API 来生成各种 Request
子类,这些子类封装了不同的 URLSessionTask
子类,以及封装应用于实例生成的所有 Request
的各种配置。Session
提供了一个 default
单例实例,并且 AF
实际上就是 Session.default
。因此,以下两个语句是等效的:
AF.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
let session = Session.default
session.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
b、创建自定义的 Session 实例
使用以下便利初始化器,并将结果存储在整个应用程序使用的单个实例中。此初始化器允许自定义 Session
的所有行为。
let session = Session.init(...)
public convenience init
(
configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
delegate: SessionDelegate = SessionDelegate(),
rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
startRequestsImmediately: Bool = true,
requestQueue: DispatchQueue? = nil,
serializationQueue: DispatchQueue? = nil,
interceptor: RequestInterceptor? = nil,
serverTrustManager: ServerTrustManager? = nil,
redirectHandler: RedirectHandler? = nil,
cachedResponseHandler: CachedResponseHandler? = nil,
eventMonitors: [EventMonitor] = []
)
c、使用 URLSessionConfiguration 创建 Session
要自定义底层 URLSession
的行为,可以提供自定义的 URLSessionConfiguration
实例。建议从改变URLSessionConfiguration.af.default
实例开始,因为它添加了 Alamofire
提供的默认 Accept-Encoding
、Accept-Language
和User-Agent headers
。
let configuration = URLSessionConfiguration.af.default
configuration.allowsCellularAccess = false
let customConfigurationSession = Session(configuration: configuration)
customConfigurationSession.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
d、SessionDelegate
SessionDelegate
实例封装了对各种 URLSessionDelegate
和相关协议回调的所有处理。默认情况下,Session
将在添加至少一个响应 handler
后立即对 Request
调用 resume()
。将 startRequestsImmediately
设置为 false
需要手动调用所有请求的 resume()
方法。
let session = Session(startRequestsImmediately: false)
session.request("https://httpbin.org/get").resume().responseJSON
{ response in
debugPrint(response)
}
输出结果为:
[Request]: GET https://httpbin.org/get
[Headers]: None
[Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 0.00031406700145453215s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)
e、Session 的 DispatchQueue
默认情况下,Session
实例对所有异步工作使用单个 DispatchQueue
。这包括 URLSession
的 delegate OperationQueue
的 underlyingQueue
,用于所有 URLRequest
创建、所有响应序列化工作以及所有内部 Session
和 Request
状态的改变。如果性能分析显示瓶颈在于 URLRequest
的创建或响应序列化,则可以为 Session
的每个工作区域提供单独的 DispatchQueue
。提供的任何自定义 rootQueue
都必须是串行队列,但 requestQueue
和 serializationQueue
可以是串行或并行队列。通常建议使用串行队列,除非性能分析显示工作被延迟,在这种情况下,使队列并行可能有助于提高整体性能。
let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")
let session = Session(
rootQueue: rootQueue,
requestQueue: requestQueue,
serializationQueue: serializationQueue
)
f、添加其他信息
❶ 添加 RequestInterceptor
Alamofire
的 RequestInterceptor
协议(RequestAdapter
& RequestRetrier
)提供了重要而强大的请求自适应和重试功能。
let policy = RetryPolicy()
let session = Session(interceptor: policy)
❷ 添加 ServerTrustManager
Alamofire
的 ServerTrustManager
类封装了域名和遵循 ServerTrustEvaluating
协议的类型实例之间的映射,这提供了定制 Session
处理 TLS
安全性的能力。这包括使用证书和公钥固定以及证书吊销检查。
let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
let managerSession = Session(serverTrustManager: manager)
managerSession.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
❸ 添加 RedirectHandler
Alamofire
的 RedirectHandler
协议定制了 HTTP
重定向响应的处理。
let redirector = Redirector(behavior: .follow)
let redirectorSession = Session(redirectHandler: redirector)
redirectorSession.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
❹ 添加 CachedResponseHandler
Alamofire
的 CachedResponseHandler
协议定制了响应的缓存,可以在 Session
和 Request
层级使用。
let cacher = ResponseCacher(behavior: .cache)
let cacherSession = Session(cachedResponseHandler: cacher)
cacherSession.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
❺ 添加 EventMonitor
Alamofire
的 EventMonitor
协议提供了对 Alamofire
内部事件的强大洞察力。它可以用来提供日志和其他基于事件的特性。
let monitor = ClosureEventMonitor()
monitor.requestDidCompleteTaskWithError =
{ (request, task, error) in
debugPrint(request)
}
let monitorSession = Session(eventMonitors: [monitor])
monitorSession.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
g、从 URLSession 创建实例
除了前面提到的便利初始化器之外,还可以直接从 URLSession
初始化 Session
。但是,在使用这个初始化器时需要记住几个要求,因此建议使用便利初始化器。其中包括:
-
Alamofire
不支持为在后台使用而配置的URLSession
。初始化Session
时,这将导致运行时错误。 - 必须创建
SessionDelegate
实例并将其作为URLSession
的delegate
,以及传递给Session
的初始化器。 - 必须将自定义
OperationQueue
作为URLSession
的delegateQueue
。此队列必须是串行队列,它必须具有备用DispatchQueue
,并且必须将该DispatchQueue
作为其rootQueue
传递给Session
。
let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.underlyingQueue = rootQueue
let delegate = SessionDelegate()
let configuration = URLSessionConfiguration.af.default
let urlSession = URLSession(configuration: configuration,
delegate: delegate,
delegateQueue: queue)
let session = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)
2、Request
a、创建请求
URLConvertible协议
可以使用遵循 URLConvertible
协议的类型来构造 URL,然后使用 URL 在内部构造 URL 请求。默认情况下,String
、URL
和URLComponents
遵循了URLConvertible
协议,允许将它们中的任何一个作为 URL 参数传递给 request
、upload
和 download
方法。
let urlString = "https://httpbin.org/get"
AF.request(urlString)
let url = URL(string: urlString)!
AF.request(url)
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
AF.request(urlComponents)
URLRequestConvertible协议
遵循 URLRequestConvertible
协议的类型可用于构造 URLRequest
。默认情况下,URLRequest
遵循 URLRequestConvertible
,允许将其直接传递到 request
、upload
和 download
方法中。
let postUrl = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: postUrl)
urlRequest.method = .post
let parameters = ["foo": "bar"]
do
{
urlRequest.httpBody = try JSONEncoder().encode(parameters)
}
catch
{
// Handle error.
print("出错了")
}
urlRequest.headers.add(.contentType("application/json"))
AF.request(urlRequest)
.responseJSON
{ response in
debugPrint(response)
}
输出结果为:
[Result]: success({
args = {
};
data = "{\"foo\":\"bar\"}";
files = {
};
form = {
};
headers = {
Accept = "*/*";
"Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
"Accept-Language" = "en;q=1.0";
"Content-Length" = 13;
"Content-Type" = "application/json";
Host = "httpbin.org";
"User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
"X-Amzn-Trace-Id" = "Root=1-600fbced-3ebfeeb20ee086fa017998b4";
};
json = {
foo = bar;
};
origin = "218.104.139.115";
url = "https://httpbin.org/post";
})
b、请求管道
一旦使用 Request
子类的初始参数或 URLRequestConvertible
创建了它,它就会通过组成 Alamofire
请求管道的一系列步骤进行传递。
请求步骤
- 初始参数(如
HTTP
方法、headers
和参数)被封装到内部URLRequestConvertible
值中。 - 对
URLRequestConvertible
值调用asURLRequest()
,创建第一个URLRequest
值。此值将传递给Request
并存储在requests
中。 - 如果
Session
有RequestAdapter
或RequestInterceptor
,则使用先前创建的URLRequest
调用它们。然后将调整后的URLRequest
传递给Request
并存储在requests
中。 -
Session
调用Request
创建的URLSessionTask
,以基于URLRequest
执行网络请求。 - 完成
URLSessionTask
并收集URLSessionTaskMetrics
(日志)后,Request
将执行其Validator
。 - 请求执行已附加的任何响应
handlers
,如responseDecodable
。
触发重试
在这些步骤中的任何一个,都可以通过创建或接收的 Error
值来表示失败,然后将错误值传递给关联的 Request
。例如,除了步骤 1 和 4 之外,上面的所有其他步骤都可以创建一个Error
,然后传递给响应 handlers
或可供重试。一旦将错误传递给 Request
,Request
将尝试运行与 Session
或 Request
关联的任何 RequestRetrier
。如果任何 RequestRetrier
选择重试该 Request
,则将再次运行完整的管道。RequestRetrier
也会产生 Error
,但这些错误不会触发重试。
失败原因
- 参数封装不能失败。
- 调用
asURLRequest()
时,任何URLRequestConvertible
值都可能创建错误。这允许初始验证各种URLRequest
属性或参数编码失败。 -
RequestAdapter
在自适应过程中可能会失败,可能是由于缺少授权token
。 -
URLSessionTask
创建不能失败。 -
URLSessionTask
可能由于各种原因带有错误地完成,包括网络可用性和取消。这些Error
值将传递回给Request
。 - 响应
handlers
可以产生任何错误,通常是由于无效响应或其他分析错误。
c、请求种类
Alamofire
执行的每个请求都由特定的类、DataRequest
、UploadRequest
和 DownloadRequest
封装。这些类中的每一个都封装了每种类型请求所特有的功能,但是 DataRequest
和 DownloadRequest
继承自一个公共的父类 Request
(UploadRequest
继承自 DataRequest
)。Request
实例从不直接创建,而是通过各种 request
方法之一从会话 Session
中自动生成。
DataRequest
DataRequest
是 Request
的一个子类,它封装了 URLSessionDataTask
,将服务器响应下载到存储在内存中的 Data
中。因此,必须认识到,超大下载量可能会对系统性能产生不利影响。对于这些类型的下载,建议使用 DownloadRequest
将数据保存到磁盘。
除了 Request
提供的属性之外,DataRequest
还有一些属性。其中包括 data
(这是服务器响应的累积 Data
)和 convertible
(这是创建 DataRequest
时使用的 URLRequestConvertible
,其中包含创建实例的原始参数)。
默认情况下,DataRequest
不验证响应。相反,必须向其中添加对 validate()
的调用,以验证各种属性是否有效。添加 validate()
确保响应状态代码在 200..<300
范围内,并且响应的 Content-Type
与请求的 Accept
匹配。通过传递 Validation
闭包可以进一步定制验证。
UploadRequest
UploadRequest
是 DataRequest
的一个子类,它封装 URLSessionUploadTask
、将 Data
、磁盘上的文件或 InputStream
上传到远程服务器。
除了 DataRequest
提供的属性外,UploadRequest
还有一些属性。其中包括一个 FileManager
实例,用于在上传文件时自定义对磁盘的访问,以及 upload
,upload
封装了用于描述请求的 URLRequestConvertible
值和确定要执行的上传类型的Uploadable
值。
DownloadRequest
DownloadRequest
是 Request
的一个具体子类,它封装了 URLSessionDownloadTask
,将响应数据下载到磁盘。DownloadRequest
除了由 Request
提供的属性外,还有一些属性。其中包括取消 DownloadRequest
时生成的数据 resumeData
(可用于以后继续下载)和 fileURL
(下载完成后下载文件对应的 URL
)。
除了支持 Request
提供的 cancel()
方法外,DownloadRequest
还包括了其他两种取消方法。
// 可以选择在取消时设置 resumeData 属性
cancel(producingResumeData shouldProduceResumeData: Bool)
// 将生成的恢复数据提供给传递进来的闭包
cancel(byProducingResumeData completionHandler: @escaping (_ data: Data?) -> Void)
d、请求状态
尽管 Request
不封装任何特定类型的请求,但它包含 Alamofire
执行的所有请求所共有的状态和功能,表示 Request
生命周期中的主要事件。请求在创建后以 .initialized
状态启动。通过调用适当的生命周期方法,可以挂起、恢复和取消 Request
。
public enum State
{
case initialized
case resumed
case suspended
case cancelled
case finished
}
- resume(): 恢复或启动请求的网络流量。如果
startRequestsImmediately
为true
,则在将响应handlers
添加到Request
后自动调用此函数。 - suspend(): 挂起或暂停请求及其网络流量。此状态下的
Request
可以继续,但只有DownloadRequest
才能继续传输数据。其他Request
将重新开始。 - cancel(): 取消请求。一旦进入此状态,就无法恢复或挂起
Request
。调用cancel()
时,将使用AFError.explicitlyCancelled
实例设置请求的error
属性。 - finished(): 如果一个
Request
被恢复并且在以后没有被取消,那么它将在所有响应验证器和响应序列化器运行之后到达.finished
状态。但是,如果在请求达到.finished
状态后将其他响应序列化器添加到该请求,则它将转换回.resumed
状态并再次执行网络请求。
e、请求进度
为了跟踪请求的进度,Request
提供了 uploadProgress
和 downloadProgress
属性以及基于闭包的 uploadProgress
和 downloadProgress
方法。与所有基于闭包的 Request APIs
一样,进度 APIs
可以与其他方法链接到 Request
之后,但是应该在添加任何响应 handlers
(如 responseJSON
)之前添加到请求中。但并不是所有的 Request
子类都能够准确地报告它们的进度,有些可能有其他依赖项来报告它们的进度。对于下载进度,只有一个要求,服务器响应必须包含 Content-Length header
。
AF.request("https://httpbin.org/get")
.uploadProgress
{ progress in
print("上传进度: \(progress.fractionCompleted)")
}
.downloadProgress
{ progress in
print("下载进度: \(progress.fractionCompleted)")
}
.responseJSON
{ response in
debugPrint(response)
}
输出结果为:
下载进度: 1.0
[Result]: success({
args = {
};
headers = {
Accept = "*/*";
"Accept-Encoding" = "br;q=1.0, gzip;q=0.9, deflate;q=0.8";
"Accept-Language" = "en;q=1.0";
Host = "httpbin.org";
"User-Agent" = "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1";
"X-Amzn-Trace-Id" = "Root=1-600f8c8f-1550b7515a19e35b7f534f0e";
};
origin = "218.104.139.115";
url = "https://httpbin.org/get";
})
对于上传进度,可以通过以下方式确定进度
- 通过作为上传
body
提供给UploadRequest
的Data
对象的长度。 - 通过作为
UploadRequest
的上传body
提供的磁盘上文件的长度。 - 通过根据请求的
Content-Length header
的值(如果已手动设置)。
f、调整和重试请求
重定向
Alamofire
的 RedirectHandler
协议提供了对 Request
的重定向处理的控制和定制。除了每个 Session
RedirectHandler
之外,每个 Request
都可以被赋予属于自己的 RedirectHandler
,并且这个 handler
将重写 Session
提供的任何 RedirectHandler
。
let redirector = Redirector(behavior: .follow)
AF.request("https://httpbin.org/get")
.redirect(using: redirector)
.responseDecodable(of: String.self)
{ response in
debugPrint(response)
}
重试
Alamofire
的 RequestInterceptor
协议由 RequestAdapter
和 RequestRetrier
协议组成。在身份验证系统中,向每个 Request
添加一个常用的 headers
,并在授权过期时重试 Request
。Alamofire
还包含一个内置的 RetryPolicy
类型,当由于各种常见的网络错误而导致请求失败时,可以轻松重试。
RequestAdapter
协议允许在通过网络发出之前检查和修改 Session
执行的每个 URLRequest
。适配器的一个常见的用途,是在身份验证后面为请求添加 Authorization header
。RequestRetrier
协议允许重试在执行时遇到错误的请求。
3、Security
在与服务器和 web
服务通信时使用安全的 HTTPS
连接是保护敏感数据的重要步骤。默认情况下,Alamofire
接收与 URLSession
相同的自动 TLS
证书和证书链验证。虽然这保证了证书链的有效性,但并不能防止中间人(MITM
)攻击或其他潜在的漏洞。为了减轻 MITM 攻击,处理敏感客户数据或财务信息的应用程序应使用 Alamofire
的 ServerTrustEvaluating
协议提供的证书或公钥固定。
a、评估服务器信任
ServerTrustEvaluting 协议
- DefaultTrustEvaluator:使用默认服务器信任评估,同时允许您控制是否验证质询提供的主机。
- RevocationTrustEvaluator:检查接收到的证书的状态以确保它没有被吊销。这通常不会在每个请求上执行,因为它需要网络请求开销。
- PinnedCertificatesTrustEvaluator:使用提供的证书验证服务器信任。如果某个固定证书与某个服务器证书匹配,则认为服务器信任有效。此评估器还可以接受自签名证书。
- PublicKeysTrustEvaluator:使用提供的公钥验证服务器信任。如果某个固定公钥与某个服务器证书公钥匹配,则认为服务器信任有效。
- CompositeTrustEvaluator:评估一个
ServerTrustEvaluating
值数组,只有在所有数组中值都成功时才成功。此类型可用于组合,例如,RevocationTrustEvaluator
和PinnedCertificatesTrustEvaluator
。 - DisabledEvaluator:此评估器应仅在调试方案中使用,因为它禁用所有求值,而这些求值又将始终认为任何服务器信任都是有效的。此评估器不应在生产环境中使用!
// 此方法提供从底层 URLSession 接收的 SecTrust 值和主机 String,并提供执行各种评估的机会
func evaluate(_ trust: SecTrust, forHost host: String) throws
ServerTrustManager
- cert.example.com:将始终在启用默认和主机验证的情况下使用证书固定
- keys.example.com:将始终在启用默认和主机验证的情况下使用公钥固定
let evaluators: [String: ServerTrustEvaluating] =
[
// 默认情况下,包含在 app bundle 的证书会自动固定。
"cert.example.com": PinnedCertificatesTrustEvalutor(),
// 默认情况下,包含在 app bundle 的来自证书的公钥会被自动使用。
"keys.example.com": PublicKeysTrustEvalutor(),
]
let manager = ServerTrustManager(evaluators: serverTrustPolicies)
b、Logging
EventMonitor
协议的最大用途是实现相关事件的日志记录。
final class Logger: EventMonitor
{
let queue = DispatchQueue(label: "xiejiapei")
// Event called when any type of Request is resumed.
func requestDidResume(_ request: Request)
{
print("Resuming: \(request)")
}
// Event called whenever a DataRequest has parsed a response.
func request(_ request: DataRequest, didParseResponse response: DataResponse)
{
debugPrint("Finished: \(response)")
}
}
let logger = Logger()
let session = Session(eventMonitors: [logger])
session.request("https://httpbin.org/get").responseJSON
{ response in
debugPrint(response)
}
输出结果为:
[Request]: GET https://httpbin.org/get
[Headers]: None
[Body]: None
[Response]: None
[Network Duration]: None
[Serialization Duration]: 4.879705375060439e-05s
[Result]: failure(Alamofire.AFError.sessionDeinitialized)
Resuming: GET https://httpbin.org/get
"Finished: failure(Alamofire.AFError.sessionDeinitialized)"
Demo
Demo在我的Github上,欢迎下载。
UseFrameworkDemo