前两篇《Alamofire 学习(一)-网络基础知识准备》和《Alamofire 学习(二)-URLSession 知识准备》,我恶补了一下网络基础知识和 URLSession 的知识,有须要的朋友能够去看看。如今终于能够开始正式学习 Alamofire 了。git
Alamofire 是 swift 写的一个很是优秀的网络请求框架,至关于 OC 中的 AFNetWork,并且与 AF 是同一家出的,其实 AFNetwork 的前缀 AF 即是 Alamofire 的缩写。它本质是基于 URLSession
的封装,让咱们网络请求相关代码更简洁易用。github 上截止如今已经 31.7k 颗⭐️了,附上 github 地址 Alamofire。github
- 链式的请求/响应方法
- URL / JSON / plist 参数编码
- 上传类型支持:文件( File)、数据( Data)、流( Stream)以及 MultipartFormData
- 支持文件下载,下载支持断点续传
- 支持使用 NSURLCredential 进行身份验证
- HTTP 响应验证
- TLS Certificate and Public Key Pinning
- Progress Closure & NSProgress
咱们先来认识一个类 SesssionManager,SesssionManager 就是对外提供的管理者,这个管理者具有整个 Alamofire 的全部功能。swift
下面是 SesssionManager 初始化部分的源码:api
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 的代理。数组
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)
}()
}
复制代码
⚠️注意: 一、设为
.background
模式: 这里的 configuration 必定要设置为.background
模式的(默认是**.default
**),否则没法实现后台下载。session二、作成单例: 这里我把 manager 作成了单例,否则在进入后台就会释放,同时网络也就会报错:
Error Domain=NSURLErrorDomain Code=-999 "cancelled"
,这样在 AppDelegate 的回调方便接收。app
调用的时候很方便:框架
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
中用单例接收:ide
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
MYBackgroundManager.shared.manager.backgroundCompletionHandler = completionHandler
}
复制代码
Request 是一个父类,它有以下几个子类:
使用起来很简单,像下面这样就实现了一个简单的 get 请求
SessionManager.default.request(urlString, method: .get, parameters: ["username":"凡几多"])
.response { (response) in
debugPrint(response)
}
复制代码
点击 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) {
复制代码
若是是直接拼接到 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: "&")
}
复制代码
一、先把参数根据 ASCII 进行了升序排序并遍历:
for key in parameters.keys.sorted(by: <) { let value = parameters[key]! 复制代码
二、把键值对经过
queryComponents
函数进行递归并进行百分号编码,而后返回一个元祖,再把元祖添加到了数组 components 中:components += queryComponents(fromKey: key, value: value) 复制代码
三、把元祖中的第一个元素和第二个元素用 = 链接,而后再用 & 符号进行了分割:
return components.map { "\($0)=\($1)" }.joined(separator: "&") 复制代码
若是是 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)
复制代码
接下来咱们分析一下内部: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 中,是为了下降耦合性。
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。
delegate[task] = request
复制代码
这一篇对 Request 有了大概的了解,下一篇咱们继续。
转载请备注原文出处,不得用于商业传播——凡几多