一、简述
在Alamofire
中为了方便管理,明确分工,Alamofire
对整个请求过程做了明确划分,并统一交由SessionManager
来管理。SessionManager
负责SessionDelegate、URLSession、URLRequest
等对象创建与管理。先看一段请求示例:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {
(response) in
switch response.result{
case .success(let json):
print("json:\(json)")
break
case .failure(let error):
print("error:\(error)")
break
}
}
这里的request
相当于Alamofire
的一个请求api
,内部实现了所有需要的请求配置,此外也封装了download、upload、stream
等请求api
供用户直接使用。一般请求流程为url -> request -> task -> response
,一般url
为String
类型的资源链接,request
才是我们请求的重中之重。在Alamofire
中对请求流程都做了任务细分,下面以request
作为主线向下探索,看看框架是如何细分的。
二、URLRequest常规配置
下面看一下常规配置:
let url = URL.init(string: "1⃣协议://2⃣主机地址/3⃣路径/4⃣参数1&参数2")!
var request = URLRequest.init(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let postData = ["username":"hibo","password":"123456"]
request.httpBody = try?JSONSerialization.data(withJSONObject: postData, options: [])
request.timeoutInterval = 30
request.cachePolicy = .useProtocolCachePolicy
需要一系列参数配置,可能根据不同需要要做更多的配置,项目中几乎每个页面都需要网络请求,对于以上的代码量,肯定是要做封装处理,提取通用代码复用,开放配置参数,供特殊需求使用。
三、对URLRequest的封装
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:HTTPMethod
枚举类型参数,设置请求类型默认get
类型 -
Parameters:
请求参数,字典类型,默认为nil
-
encoding:
支持编码格式,有三种类型:URLEncoding、JSONEncoding、PropertyListEncoding
默认URLEncoding
-
headers:
请求头参数设置默认为空
以上为对外引出常规参数,用户根据需求设置,内部有默认值,用户不设置则启用默认值(通用属性值)。以上几个参数即是URLRequest
的属性,请求中的主要属性。
四、参数处理
在开发中,我们常常使用GET
请求或POST
请求。GET
请求,请求参数是拼接在地址上的&或/符合分隔,常用于获取数据;POST
请求将参数封装在请求体中,常用数据提交。
下面看一下框架是如何处理这些参数的:
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
}
- 没有参数直接返回
URLRequest
对象 - 有参数则需要对分别处理参数,
GET
需要拼接,POST
需要添加添加至httpBody
请求体中 -
percentEncodedQuery
获取域名后的参数并与请求参数拼接
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: "&")
}
- 传入一个字典参数
- 对字典
key
的ASCII码
进行排序,重新组合成一个元组数组 -
queryComponents
内部进行递归操作,将key
和value
进行百分号编码,并放回元组中 - 通过
map
结合元组元素形成新的数组,在通过joined
函数将数组元素拼接,两个元素直接用&
符分隔
queryComponents
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
- 递归操作,遍历到数据的每一层,直到键值对的值为非字典和数组类型,开始对
key
和value
做百分号编码操作 -
escape
为编码函数
GET请求
通过
query
来拼接并做百分号编码所有的键值对,处理成"username=hibo&password=123456"
这种形式,直接拼接至请求域名上,request.url
为域名,上面通过urlComponents.percentEncodedQuery
已经拆分出来了资源路径因此,拼接的连接为:host+资源路径(s=api/test/list/)+&+参数
POST请求
在没有Content-Type
配置的情况下给默认配置。如下:
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
把
query
拼接后的参数转换为Data
类型数据,传给请求体httpBody
。至此GET或其他请求的参数就已经设置完毕。URLRequest
设置完成后,就可以创建任务发送请求了。下面看一下框架怎么处理。
五、发送请求
顺着代码找到另一个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)
}
}
这里只做了三步处理:
在这个方法中并没有发起请求,而是将发起任务的
session
和request
,交给了DataRequest
,对任务分层,manager
说我这只管维持你们需要的参数,具体任务自己带回家去做,分工明确,思路更清晰。绑定绑定返回
task
和request
,以便SessionDelegate
做任务下发,在SessionDelegate
中通过task获取taskDelegate
对象有
manager
管理任务的启动request.resume()
DataRequest
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() }
}
- 通过枚举来配置不同的任务代理
TaskDelegate
分多个子类,具有不同职能。子类如下:
DataTaskDelegate
DownloadTaskDelegate
UploadTaskDelegate
根据不同任务类型,由Request
类确定不同的处理类,stream
类的任务有基类处理。该类中都对外声明了相应代理事件的闭包,以便对外传递代理消息。如下:
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
该闭包都是在Request
类中或者拓展方法中实现的,实际不是实现只是桥接到界面中实现,从而代理事件传递直接传到界面闭包。
总结
1、SessionManager
-
httpAdditionalHeaders
参数配置 - 创建
URLRequest
- 创建
URLSession
-
URLSessionDelegate
代理移交给SessionDelegate
- 发起任务请求
2、SessionDelegate
- 实现所有网络请求的代理方法,并对外声明闭包,通过闭包向外传递代理响应事件
- 如果外部没有实现接收代理事件的闭包,会将代理事件通过
task
标识移交给TaskDelegate
中具体的子类来处理
3、Request
- 负责任务的创建,保留
URLSession
对象即URLRequest
对象 - 负责任务下发,下发至
TaskDelegate
中 - 实现数据传递方法,对外设置闭包参数,内部将闭包桥接至
TaskDelegate
中,即实现TaskDelegate
中对外声明的闭包 - 方法返回
self
实现链式调用
4、TaskDelegate
- 创建子类,负责具体任务实现
- 对外声明闭包向外传递代理消息
- 改类对象在
Request
中使用