一、简介
前两篇《Alamofire 学习(一)-网络基础知识准备》和《Alamofire 学习(二)-URLSession 知识准备》,我恶补了一下网络基础知识和 URLSession 的知识,有需要的朋友可以去看看。现在终于可以开始正式学习 Alamofire 了。
1、Alamofire 是什么
Alamofire 是 swift 写的一个非常优秀的网络请求框架,相当于 OC 中的 AFNetWork,而且与 AF 是同一家出的,其实 AFNetwork 的前缀 AF 便是 Alamofire 的缩写。它本质是基于 URLSession
的封装,让我们网络请求相关代码更简洁易用。github 上截止现在已经 31.7k 颗⭐️了,附上 github 地址 Alamofire。
2、Alamofire 功能特性
- 链式的请求/响应方法
- URL / JSON / plist 参数编码
- 上传类型支持:文件( File)、数据( Data)、流( Stream)以及 MultipartFormData
- 支持文件下载,下载支持断点续传
- 支持使用 NSURLCredential 进行身份验证
- HTTP 响应验证
- TLS Certificate and Public Key Pinning
- Progress Closure & NSProgress
二、SesssionManager
我们先来认识一个类 SesssionManager,SesssionManager 就是对外提供的管理者,这个管理者具备整个 Alamofire 的所有功能。
1、SesssionManager 的初始化
下面是 SesssionManager 初始化部分的源码:
public init(
configuration: URLSessionConfiguration = URLSessionConfiguration.default,
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
可以看到它初始化了 session,其中 configuration 是默认了 .default
的模式,并将 代理移交,通过创建 SessionDelegate 这个专门处理代理的类来实现 URLSession 的代理。
2、代理完成回调
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
三、后台下载
上篇讲了 URLSession 的后台下载,现在再来看一下 Alamofire 的后台下载吧。
我先封装了一个后台下载管理类的单例 ,
MYBackgroundManager:
struct MYBackgroundManager {
static let shared = MYBackgroundManager()
let manager: SessionManager = {
let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgcooci.AlamofireTest.demo")
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = 10
configuration.timeoutIntervalForResource = 10
return SessionManager(configuration: configuration)
}()
}
⚠️注意:
1、设为.background
模式:
这里的 configuration 一定要设置为.background
模式的(默认是.default
),不然无法实现后台下载。2、做成单例:
这里我把 manager 做成了单例,不然在进入后台就会释放,同时网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
,这样在 AppDelegate 的回调方便接收。
调用的时候很方便:
MYBackgroundManager.shared.manager
.download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileUrl = documentUrl?.appendingPathComponent(response.suggestedFilename!)
return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
}
.response { (downloadResponse) in
print("下载回调信息: \(downloadResponse)")
}
.downloadProgress { (progress) in
print("下载进度 : \(progress)")
}
在 AppDelegate 的handleEventsForBackgroundURLSession
中用单例接收:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
四、Request
Request 是一个父类,它有如下几个子类:
- DataRequest
- DownloadRequest
- UploadRequest
-
StreamRequest
不同的 request 有不同的作用,各司其职。
1、使用
使用起来很简单,像下面这样就实现了一个简单的 get 请求
SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡几多"])
.response { (response) in
debugPrint(response)
}
2、源码解析
点击 request 进去,可以看到源码是下面这样的:
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
var originalRequest: URLRequest?
do {
originalRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
return request(encodedURLRequest)
} catch {
return request(originalRequest, failedWith: error)
}
}
可以看到先是根据传进来的 url、method 和 headers 创建了一个 URLRequest,然后对 parameters 进行 URLEncoding 编码,最后返回一个 DataRequest。
这里我们详细看一下 encoding.encode 里是如何编码的,
这里判断了 method 的类型,
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
(1)get 请求
如果是直接拼接到 url 后面的 method 类型(如 get),假设我们的请求为 get 请求,则执行下面的代码:
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
假设我们的 get 请求为:
http://www.douban.com/j/app/radio/channels?username="fanjiduo"&&password="123456"
那么它的 urlComponents 是这样的:
http://www.douban.com/j/app/radio/channels
- scheme : "http"
- host : "www.douban.com"
- path : "/j/app/radio/channels"
我们发现它是把路由部分 urlComponents 进行了百分号编码:
(urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "")
然后对参数部分 parameters 也进行了百分号编码:
query(parameters)
我们点击 query 进去看一下它的具体实现

private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
1、先把参数根据 ASCII 进行了升序排序并遍历:
for key in parameters.keys.sorted(by: <) { let value = parameters[key]!
2、把键值对通过
queryComponents
函数进行递归并进行百分号编码,然后返回一个元祖,再把元祖添加到了数组 components 中:components += queryComponents(fromKey: key, value: value)
3、把元祖中的第一个元素和第二个元素用 = 连接,然后再用 & 符号进行了分割:
return components.map { "\($0)=\($1)" }.joined(separator: "&")
(2)post 请求
如果是 post 请求,那么 encoding.encode 又是如何编码的呢?
post 请求会在请求头中多加一个 Content-Type:
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
参数则不再放到请求头里了,而是放在 urlRequest.httpBody中,并且进行了 .data 处理:
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
3、task 和 request 的关系
接下来我们分析一下内部:url -> request -> task 的过程。这个过程中还建立了 task 以及 request 之间的绑定关系。
继续上面的源码分析:
return request(encodedURLRequest)
点 request 进去看一下源码:
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(originalRequest, failedWith: error)
}
}
我们看到这行代码:
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
点击 Requestable 进去:
struct Requestable: TaskConvertible {
let urlRequest: URLRequest
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.sync { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
}
我们发现 Requestable 其实是一个结构体,帮助 DataRequest 创建了一个包含 task 的结构体对象,相当于 DataRequest 的一个助理,帮 Requestable 创建了 task。
之所以这样写,而不直接把 task 创建写在 DataRequest 中,是为了降低耦合性。
-
初始化 request
通过助理 Requestable 得到结构体 originalTask 以后,就可以得到下面的 task,并且初始化出 request 了:
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
这个初始化方法是写在父类 Request 里的,
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = DataTaskDelegate(task: task)
self.originalTask = originalTask
case .download(let originalTask, let task):
taskDelegate = DownloadTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = UploadTaskDelegate(task: task)
self.originalTask = originalTask
case .stream(let originalTask, let task):
taskDelegate = TaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
可以看出这里传递不同的枚举值,初始化不同的 task 和delegate。
- 绑定 task 和 request
下面的代码对 task 和 request 进行了绑定。方便在 SessionDelegate 下发任务,task 直接检索,request 方便直接获取。
delegate[task] = request
这一篇对 Request 有了大概的了解,下一篇我们继续。
转载请备注原文出处,不得用于商业传播——凡几多
网友评论