😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊
Alamofire 目录直通车 --- 和谐学习,不急不躁!
上一篇 Alamofire-后台下载 其中就介绍了关于
SesssionManager
到SessionDelegate
的分层!下面我在总结一下,然后开始一个Alamofire
非常重要的模块Request
!
一、SesssionManager的总结
-
SesssionManager
就是对外提供的管理者,这个管理者具备整个Alamofire
的所有功能。-
request
请求、download、upload、stream
- 请求头信息设置以及多表单头信息设置
-
session
直接对外提供,方便自由处理 - 初始化暴露,方便自由工厂构造,比如
URLSessionConfiguration
的配置模式 - 重试以及适配请求
- 闭包对外提供:
backgroundCompletionHandler
后台下载回来监听闭包 - 其中一个非常重要的点就是
SesssionManager
把Session
的代理移交给了一个专门的类 :SessionDelegate
-
SessionDelegate
实现了URLSessionDelegate
、URLSessionTaskDelegate
、URLSessionDataDelegate
、URLSessionDownloadDelegate
、URLSessionStreamDelegate
等代理-
更多有意思的是:
SessionDelegate
还提供了对外的闭包,意味着所有的内部实现的代理情况,再外界都可以进行监听。当然这个所有的对外闭包分为两种情况:- 在原来代理回调的内部添加闭包执行。
- 另一种是二选一,如果代理回来就不执行下层代理下发,执行对外闭包回调
总结:
SesssionManager
负责创建和管理Request
对象及其底层NSURLSession
首先给大家贴出一张非常熟悉的图,看懂了这张图对下面理解 Request
的帮助大大的
- 从上面这张图可以看出,我们的对外模块是
SesssionManager
,他给外界的用户提供了很多的功能 - 但是这些工作的真正实现者是由
iOS、Android、前端、后台、测试
实现的! - 其中单拿
iOS
模块的任务来说,有首页、发现、我的、SDK、视频....
模块要实现,但是我们的项目经理有可能都不知道这些到底是什么,怎么实现!所有来说如果全部交给SesssionManager
来实现,显然耦合性过强,还有任务乱七八糟,没有体现一个牛逼项目分层架构的效果。所以在iOS
任务细化和SesssionManager
之间就缺了一个小管理者,对下:他知道具体事务和调度。对上:他能和SesssionManager
协调配合。那就是Request
二、Request
1. Request参数编码
-
首先说明一下
Alamofire
支持编码格式,具体格式差异根据名字很容易理解,实在不能理解的可以自行百度查漏补缺- URLEncoding
- JSONEncoding
- PropertyListEncoding
let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
通过这句代码还编码请求
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
代码自行查阅,这里总结一下
-
首先取出请求方法,根据不同的请求方法,参数编码是不同的
-
.get, .head, .delete
这三个方法是把参数直接拼接到URL
后面 - 其他通过请求体 (
httpBody
) 的形式编码
-
因为我们的请求是通过
ASCII编码
的,所以要进行百分号编码,第一步就是对当前请求的所有路由百分号编码参数便利编码, 拼接拿出
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
有小到大进行排序 -
queryComponents
这个方法代码过度省略。- 里面进行递归参数,
- 把
key和value
取出,然后进行了百分号编码。 - 放进元组保存,形成参数对
- 外面讲元组加入数组中
- map 映射
($0)=\($1)
- 元素之间键入一个分隔符号
&
普通方法就直接拼接到URL的后面,例如POST方法就是把这些编码好的参数对放入请求体中。其中还要加入Content-Type
的请求头
2. Request内部关系梳理
探索完 request
繁琐事务之一的参数编码之后,开始分析内部:url -> request -> task
的过程。这个过程中还建立 task以及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)
}
}
- 内部创建
Requestable
来帮助下层的DataRequest
创建Task
,也是常规说法,任务分层,架构思路更清晰 - 绑定
task 和 request
, 方便在SessionDelegate
下发任务,task
直接检索,request
方便直接获取 - 直接任务
request.resume()
启动
-
Request
初始化的时候利用了枚举的便利性,构造创建,相关信息保存
可能很多小伙伴这个时候就会有疑虑,你不就是通过SessionDelegate
实现代理, 为什么这里还要有一个 DataTaskDelegate
首先一定要明白
SessionDelegate
是所有Session
的代理遵循者,任何的代理都会来到这里响应。DataTaskDelegate
是我们具体繁琐任务实现者,这里面所有方法都是由SessionDelegate
下发响应的。说白了就是整体与局部的关系
SessionDelegate
是事件总响应者,但是具体的事务应该交给具体的人去执行,不能因为在SessionDelegate
能够拿到所有的响应,那么所有的代码都罗列在这里,很显然如果那样做必然会导致混乱,我们根据不同的需求,响应总代理然后根据需求的不同交给专业的人去做专业的事。耦合性大大降低,架构的分层更加明显。其中还有异步非常重要的点:
delegate[task] = request
给我们的SessionDelegate
提供一个方便及时获得能力
open func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
// 省略属性闭包回调的情况
if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
}
- 这段代码典型的通过我们前面建立的绑定关系,通过每次代理回调的
task
找到相应的Request
然后因为属性关系,直接拿出Request
的属性delegate 来处理相关事务
func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
temporaryURL = location
guard
let destination = destination,
let response = downloadTask.response as? HTTPURLResponse
else { return }
let result = destination(location, response)
let destinationURL = result.destinationURL
let options = result.options
self.destinationURL = destinationURL
do {
if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
try FileManager.default.removeItem(at: destinationURL)
}
if options.contains(.createIntermediateDirectories) {
let directory = destinationURL.deletingLastPathComponent()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
try FileManager.default.moveItem(at: location, to: destinationURL)
} catch {
self.error = error
}
}
- 文件下载完毕转移存储地址,文件处理相关 以及error情况
- 上面就是最典型的繁重恶心操作,必然是不需要被
SessionDelegate
所需要知道的!
我们从
SessionManager
->Request
这一过程明面上就是直接交付,但是背地都是通过代理响应联通的:SessionDelegate
->具体的Request的delegate
。看到这里,是不是感觉非常的爽!优秀的框架总是能够给你在思想方面带来很大的提升,我们平时开发的时候也会有很恶心的耦合度,通讯问题。那么想必Alamofire
的设计思路肯定能够给你带来一定的启示!就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!