一、回顾
SessionManager.png在前面源码探索中,
SessionManager
管理Request
和SessionDelegate
的创建,并通过task
绑定Request
和SessionDelegate
对象;Request
负责请求的参数的配置,以及task
不同任务的创建,创建连接外部(发送请求对象)和TaskDelegate
的方法,通过闭包参数,获取TaskDelegate
代理事件的内容;TaskDelegate
代理事件是由SessionDelegate
通过task
移交的。总结图:
以上处理的目的是对任务做分层处理,使结构清晰。
二、RequestAdapter-适配器
在Request
文件下还存在一个协议RequestAdapter
。在Manager
中创建调用。如下:
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)
}
}
联系上下文,adapter
并没有被初始化,怎么回事呢?下面看一下是如何定义的:
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
///
/// - parameter urlRequest: The URL request to adapt.
///
/// - throws: An `Error` if the adaptation encounters an error.
///
/// - returns: The adapted `URLRequest`.
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
一个协议内部定义了一个方法,上面定义可以以某种方式检查并适应URLRequest
,实际是告诉我们,根据需要遵循该协议并实现该方法。一脸懵逼,实现它干嘛呢?其实也不难猜测,既然给我们该类型,肯定是方便我们设置参数,如token、device、vision
等等这些公共参数,其实可以设置的,那下面就来试试。
1、添加公共参数
class MyAdapter: RequestAdapter{
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var request = urlRequest
request.setValue("hibotoken", forHTTPHeaderField: "token")
request.setValue("device", forHTTPHeaderField: "iOS")
request.setValue("vision", forHTTPHeaderField: "1.0.0")
return request
}
}
下面设置adapter并发送一个请求:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.adapter = MyAdapter()
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
}
}
- 在
SessionManager
中定义了adapter
对象,这里就对其赋值一个实现了adapt
方法的子类对象
这里在请求前在adapt
中设置了请求头,那么就运行一下,通过抓包看看公共参数是否添加成功:
添加成功,开发中的参数以后就可以单独使用该方法进行管理了。
2、重定向
class redireatAdapter: RequestAdapter{
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
let newURLRequest = URLRequest.init(url: URL.init(string: "http://onapp.yahibo.top/public/?s=api/test")!)
return newURLRequest
}
}
直接修改原请求地址,重定向至其他地址。
为什么会添加公共参数,或重定向?
代码追踪,追踪到最终使用位置如下:
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)
}
}
func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
guard let adapter = adapter else { return self }
return try adapter.adapt(self)
}
这里调用了该方法,这里判断了adapter
是否存在,不存在直接使用前面创建并设置好参数的URLRequest
对象,如果存在则adapter
调用adapt
方法,将当前URLRequest
对象传出去加工处理。
三、validate-自定义验证
开发中经常会根据不同的状态码来处理,比如开发中需要将某一结果定义为错误请求,在error
中来做处理,那么在该框架中我们可以使用validate
来重新验证,并定义请求结果。代码如下:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
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
}
}.validate{ (request, response, data) -> Request.ValidationResult in
print(response)
guard let _ = data else {
return .failure(NSError(domain: "没有数据啊", code: 0, userInfo: nil))
}
guard response.statusCode == 200 else {
return .failure(NSError(domain: "是不是哪弄错了", code: response.statusCode, userInfo: nil))
}
return .success
}
- 通过链式方法调用
validate
验证方法,根据具体需求添加验证逻辑 - 返回数据为空,定义为错误信息
-
statusCode != 200
认为是错误请求
通过以上试用,我们对Alamofire
又有了更多的了解,无论是监听请求进度还是这种验证均以链式调用为主,方便快捷。
四、RequestRetrier-重新请求
很多情况下,如果网络请求失败,我们是有重新请求的需求,那么该框架也提供了这样的方法,请求失败都会调用代理方法:urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
。而框架就在该代理方法中做了如下处理:
if let retrier = retrier, let error = error {
retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
guard shouldRetry else { completeTask(session, task, error) ; return }
DispatchQueue.utility.after(timeDelay) { [weak self] in
guard let strongSelf = self else { return }
let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
if retrySucceeded, let task = request.task {
strongSelf[task] = request
return
} else {
completeTask(session, task, error)
}
}
}
} else {
completeTask(session, task, error)
}
- 当请求错误,先判断
retrier
是否被定义如果定义则调用should
方法 - 这里
retrier
是一个继承自RequestRetrier
协议的类对象
RequestRetrier
/// A type that determines whether a request should be retried after being executed by the specified session manager
/// and encountering an error.
public protocol RequestRetrier {
/// Determines whether the `Request` should be retried by calling the `completion` closure.
///
/// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
/// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
/// cleaned up after.
///
/// - parameter manager: The session manager the request was executed on.
/// - parameter request: The request that failed due to the encountered error.
/// - parameter error: The error encountered when executing the request.
/// - parameter completion: The completion closure to be executed when retry decision has been determined.
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
- 与
RequestAdapter
一样,需要定义类并实现方法
创建子类并继承协议,实现协议方法如下:
class MyRetrier: RequestRetrier{
var count: Int = 0
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
if count<3 {
completion(true,2)
count += 1
}else{
completion(false,2)
}
}
}
- 设置重新请求次数,为3次
- 调用内部实现的闭包,向内传值,告诉内部重新请求还是,终止请求
-
completion
有两个参数shouldRetry
为是否请求,timeDelay
为延时请求的延时时间,这里设置为2秒 - 延时请求避免,无效请求
下面就可以设置一个错误连接发送请求尝试一下:
let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list2"
let url = URL.init(string: urlStr)!
Alamofire.SessionManager.default.retrier = MyRetrier()
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
}
}.validate{ (request, response, data) -> Request.ValidationResult in
print(response)
guard let _ = data else {
return .failure(NSError(domain: "没有数据啊", code: 10086, userInfo: nil))
}
if response.statusCode == 404 {
return .failure(NSError(domain: "密码错误", code: response.statusCode, userInfo: nil))
}
return .success
}
- 和
RequestAdapter
使用方法一致,需要配置SessionManager的retrier
属性
五、Response-响应结果
Alamofire
对请求到的数据进行了处理再返回给我们,以上请求我们都调用了responseJSON
方法来获取最终数据,下面看一下responseJSON
内部做了哪些处理:
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
联系上文可知responseJSON
是DataRequest
的一个扩展方法,继承自Request
类,因此可以进行链式调用。该方法内部继续调用了response
方法。方法如下:
public func response<T: DataResponseSerializerProtocol>(
queue: DispatchQueue? = nil,
responseSerializer: T,
completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
-> Self
{
delegate.queue.addOperation {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
}
return self
}
- 方法内部对请求结果进行了序列化处理
- 将序列化的结果封装至
DataResponse
对象中
以上其实并没有看到我们熟悉的序列化,再继续搜索,找到如下代码:
public static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
- 序列化结果封装至
Result
对象中 -
Result
对象最终封装至DataResponse
对象中来管理
从上面代码能够发现response
对象管理了请求过程中所有参数:
var dataResponse = DataResponse<T.SerializedObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: self.timeline
)
因此在请求结果中,我们能够很方便的拿到所有我们需要的信息。
六、Timeline-时间轴
为什么有时间轴,在网络请求中,我们需要准确的知道请求耗时,以便于前端或后台做优化处理。下面就看一下Alamofire
的时间轴是如何设计的。
首先我们能够看到,任务是在队列中执行的:
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)
}
}
队列是在SessionManager
中创建,Manager
真是什么都管啊。代码如下:
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
- 设置标识绑定了当前设备的
UUID
- 该队列是管理发起的任务,和时间轴没有关系
紧接着初始化TaskDelegate
对象。如下:
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)
}
}
通过.data(originalTask, task)传入任务task,来初始化TaskDelegate对象如下:
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
- 设置最大并发量为1,让任务顺序执行
- 初始化的队列默认为挂起状态,因为任务还没有开启
1、startTime-记录发起请求时间
任务的创建与执行在Request中进行,代码如下:
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
- 该
resume
在SessionManager
中调用执行 - 判断任务是否存在如果存在继续执行,因为有任务会被挂起,这里重新启动
-
task
不存在,说明任务已结束,队列启动执行其他任务 - 启动任务前记录请求初始时间,因为有挂起情况,这里对
startTime
做了判空操作
2、endTimer-记录请求结束时间
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() }
}
- 创建并分类任务代理,以便任务下发
- 记录任务结束时间
上面代码做了一个初始化,为什么说是结束时间呢,因为队列为同步队列,上次请求任务结束后才会执行。即请求完成后,代码如下:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
-
queue.isSuspended = false
恢复队列,恢复后上面提到的记录时间任务即可加入到队列中执行
3、initialResponseTime-初始化响应时间
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
- 初始化数据响应时间,不同任务对应的都有初始化方法,如下载任务,上传任务
4、TimeLine-时间轴设置
在响应初始化中,初始化时间轴:
extension DataRequest {
@discardableResult
public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error,
timeline: self.timeline
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
}
- 时间轴是要面向开发的,因此在响应初始化时,被封装至
Response
中
初始化时间轴,对前面的时间记录做统一管理:
extension Request {
var timeline: Timeline {
let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
return Timeline(
requestStartTime: requestStartTime,
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
}
}
时间轴初始化,计算请求间隔,序列化时间间隔:
public init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
时间轴TimeLine
记录了请求过程中的操作时间点,并计算了每部操作的时间间隔,在请求结束后封装至Response
中。这里通过队列来同步请求中的操作,以保证startTime、endTime
的准确性,其他时间记录是在请求代理回调中设置。
TimeLine:
timeline.png
网友评论