通过前面对
Alamofire
的一些了解和使用,我们可以看到,在整个框架中,围绕着Request
设计了很多的特性,所以,Request
请求是所有请求核心基础所在。那么接下来,我们就来剖析一下这个重要的模块Request
。
请求流程
首先,我们来看一下这段代码:
Alamofire.request("https://httpbin.org/get")
这是一段非常简单的网络请求的代码,我们来看一下这个Request
中究竟干了些什么。进入源码查看:
@discardableResult
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
return SessionManager.default.request(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
可以看到,request
函数内部还调用了SessionManager
的request
方法,这就说明了网络请求的出发点是来自SessionManager
,很明显,Alamofire.swift
这个文件应该是最顶层的封装,然后调用下层文件SessionManager.swift
。好,我们接着来看:
@discardableResult
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)
}
}
看到,该函数内部创建了一个Request
对象,然后把参数编码到这个Request
中,后又调用了内部的一个request
函数,参数即为上边Request
对象。来吧,接着来看:
@discardableResult
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)
}
}
- 这个函数的权限是
open
的,所以,是可以使用SessionManager.default.request
来发起请求的,但是,参数是_ urlRequest: URLRequestConvertible
。URLRequestConvertible
协议的目的是对URLRequest
进行自定义的转换,因此,在获得转换后的URLRequest
后,需要用URLRequest
生成task
,这样才能发起网络请求,在Alamofire
中,但凡是request
开头的函数,默认的都是DataRequest
类型,现在有了URLRequest
还不够,还需要检测她能否生成与之相对应的task
。- 在上边的函数中,用到了
DataRequest.Requestable
,Requestable
其实一个结构体,他实现了TaskConvertible
协议,因此,它能够用URLRequest
生成与之相对应的task
。接下来就初始化DataRequest
,然后真正的开始发起请求。
OK,到这里,我们来简单看一下这个请求过程是怎样的:
Request
请求依赖关系
Request
类,主要用于发送网络请求、接收response
、关联服务器返回的数据、管理task
。
首先,Request
是DataRequest
、DownloadRequest
、UploadRequest
、StreamRequest
的基类,我们来看看相关属性:
// MARK: Properties
/// The delegate for the underlying task.
open internal(set) var delegate: TaskDelegate {
get {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
return taskDelegate
}
set {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
taskDelegate = newValue
}
}
/// The underlying task.
open var task: URLSessionTask? { return delegate.task }
/// The session belonging to the underlying task.
public let session: URLSession
/// The request sent or to be sent to the server.
open var request: URLRequest? { return task?.originalRequest }
/// The response received from the server, if any.
open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
/// The number of times the request has been retried.
open internal(set) var retryCount: UInt = 0
let originalTask: TaskConvertible?
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
var validations: [() -> Void] = []
private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()
这些属性就很简单了,都很好理解的,最重要的,我们来关注一下Request
的初始化方法:
// MARK: Lifecycle
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() }
}
可以看到,需要发起一个Request
请求,我们只需要一个task
就OK了。
- 第一个参数
session
,主要用于CustomStringConvertible
和CustomDebugStringConvertible
这两个协议的实现方法中获取特定的数据。 - 第二个参数是
requestTask
,这是一个枚举类型,看一下:
enum RequestTask {
case data(TaskConvertible?, URLSessionTask?)
case download(TaskConvertible?, URLSessionTask?)
case upload(TaskConvertible?, URLSessionTask?)
case stream(TaskConvertible?, URLSessionTask?)
}
- 我们在初始化
Request
的时候,只需要传递requestTask
这个枚举值,我们就得到了两个重要的数据:Request
的类型和相对应的task
。这一变成手法的运用,大大提高了代码的质量。 -
RequestTask
枚举中和选项绑定的数据有两个,TaskConvertible
表示原始的对象,该对象实现了TaskConvertible
协议,能够转换成task
。URLSessionTask
是原始对象转换后的task
。因此衍生出一种高级使用方法的可能性,可以自定义一个类,实现TaskConvertible
协议,就能够操纵task
的转换过程,很灵活。
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
这一行代码给代理的queue
添加了一个操作,队列是先进先出原则,但是可以通过isSuspended
暂停队列内部的操作。
关于参数编码
首先,Alamofire
支持的编码格式如下:
URLEncoding
JSONEncoding
PropertyListEncoding
看下如下代码:
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
的请求头。
总结
读的越多,写的越多,你就会发现,Alamofire
中的函数的设计很厉害,不是一时半会能够全部串联的。
由于知识水平有限,如有错误,还望指出,最后,常规推荐Cooci老师的文章。