本篇承接上一篇 Alamofire發(fā)起網(wǎng)絡(luò)請求初探 來繼續(xù)探索在網(wǎng)絡(luò)請求發(fā)起過程中涉及的一些有用的細(xì)節(jié).
RequestAdapter
RequestAdapter
是一個(gè)協(xié)議,提供了在發(fā)起請求之前,對request
進(jìn)行最后處理的能力,先來看看具體使用:
SessionManager.default.adapter = Adapter()
SessionManager.default.request("your url", method: .get, parameters: ["p0":"111","p2":"222"])
.response { (response) in
debugPrint(response)
}
class Adapter:RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var request = urlRequest
request.setValue("aaa", forHTTPHeaderField: "aheader")
return request
}
}
- 上面的例子設(shè)置了
Adapter
, 所有在SessionManager.default
發(fā)起請求之前,都會(huì)來到Adapter 類
的adapt
方法中 - 具體的使用場景可能有:
添加公共的頭信息
替換為一個(gè)全新的請求,但是這個(gè)請求的參數(shù)你可能要自己進(jìn)行序列化.來看源代碼:
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
//這里傳入了 adapter 屬性
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)
}
}
struct Requestable: TaskConvertible {
let urlRequest: URLRequest
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
//在這里又傳遞了 adapt
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.sync { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
}
extension URLRequest {
func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
//在這里判斷是否設(shè)置了 adapter
guard let adapter = adapter else { return self }
return try adapter.adapt(self)
}
}
在使用 adapter
創(chuàng)建了 Task 之后,在后面就發(fā)起請求了. 并沒有走參數(shù)序列化的流程.
Timeline
Alamofire
在得到網(wǎng)絡(luò)的響應(yīng)后,會(huì)打印有關(guān)本次請求的時(shí)間信息.
Timeline: {
"Request Start Time": 588155784.670,
"Initial Response Time": 588155784.969,
"Request Completed Time": 588155784.972,
"Serialization Completed Time": 588155784.972,
"Latency": 0.300 secs,
"Request Duration": 0.302 secs,
"Serialization Duration": 0.000 secs,
"Total Duration": 0.302 secs
}
這里有許多有用的信息,比如Total Duration
代表一次請求從發(fā)起到接收到響應(yīng)的總時(shí)間,方便觀測接口性能.
這些時(shí)間的是怎么統(tǒng)計(jì)的呢?
- 統(tǒng)計(jì)時(shí)間一般都是兩個(gè)絕對時(shí)間點(diǎn)做差,來看看
Alamofire
是如何做的.
open class Request {
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
//startTime在 resume 之前被初始化,記錄時(shí)間點(diǎn)
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
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
//endTime 在 Request 的父類中進(jìn)行了初始化
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
}
open internal(set) var delegate: TaskDelegate {
get {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
return taskDelegate
}
set {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
taskDelegate = newValue
}
}
值得注意的是 endTime
的初始化時(shí)機(jī), 被放到了 delegate.queue
中.來看看 delegate.queue
open class TaskDelegate: NSObject {
init(task: URLSessionTask?) {
_task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
}
可以看到 delegate.queue
是一個(gè)串行隊(duì)列,并且處于掛起狀態(tài). 所以想要使 Request
的endTime
的初始化方法被執(zhí)行, 需要了解 queue
何時(shí)取消掛起. queue
取消掛起有兩處
open class TaskDelegate: NSObject {
@objc(URLSession:task:didCompleteWithError:)
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
}
}
}
open class 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]
)
}
}
取消掛起有兩個(gè)地方:
-
task
完成時(shí) - 請求將要發(fā)起時(shí),如果
task
創(chuàng)建失敗則沒有必要統(tǒng)計(jì)時(shí)間
具體是怎么被打印出來的?
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
}
// DataRequset 繼承 Request
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 struct Timeline {
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
}
}
//這里是打印的格式化
extension Timeline: CustomStringConvertible {
public var description: String {
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
// NOTE: Had to move to string concatenation due to memory leak filed as rdar://26761490. Once memory leak is
// fixed, we should move back to string interpolation by reverting commit 7d4a43b1.
let timings = [
"\"Latency\": " + latency + " secs",
"\"Request Duration\": " + requestDuration + " secs",
"\"Serialization Duration\": " + serializationDuration + " secs",
"\"Total Duration\": " + totalDuration + " secs"
]
return "Timeline: { " + timings.joined(separator: ", ") + " }"
}
}
public struct DefaultDataResponse {
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?,
timeline: Timeline = Timeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}
}
重試機(jī)制
有時(shí)一些比較重要的接口,如果請求失敗.失敗時(shí)需要重試. Alamofire
也優(yōu)雅地為我們提供了重試機(jī)制.
SessionManager.default.retrier = Retrier()
SessionManager.default.request("your url", method: .get, parameters: ["p0":"111","p2":"222"])
.response { (response) in
debugPrint(response)
}
class Retrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
print("manager = \(manager)")
print("request = \(request)")
print("error = \(error)")
completion(true,1)
//在必要時(shí)調(diào)用,不然會(huì)引起無限重試
completion(false,0)
}
}
何時(shí)重試?
extension SessionDelegate: URLSessionTaskDelegate {
open func 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)
}
}
}
}
open class SessionManager {
func retry(_ request: Request) -> Bool {
guard let originalTask = request.originalTask else { return false }
do {
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
if let originalTask = request.task {
delegate[originalTask] = nil // removes the old request to avoid endless growth
}
request.delegate.task = task // resets all task delegate data
request.retryCount += 1
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
task.resume()
return true
} catch {
request.delegate.error = error.underlyingAdaptError ?? error
return false
}
}
}
- 在
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
方法中,檢測是否發(fā)生錯(cuò)誤.如果發(fā)生錯(cuò)誤就調(diào)用我們設(shè)置的retrier
的 'should' 方法決定是否重試. - 根據(jù)我們設(shè)置的延時(shí)時(shí)間,延時(shí)一段時(shí)間后調(diào)用
SessionManger
的func retry(_ request: Request) -> Bool
方法執(zhí)行真正的重試操作
響應(yīng)驗(yàn)證 (Response Validation)
SessionManager.default.request(urlStr, method: .get, parameters: ["p0":"aaa","p1":"bbb"])
.response { (response) in
debugPrint(response)
}.validate { (request, response, data) -> Request.ValidationResult in
guard let _ = data else{
return .failure(NSError.init(domain: "custom failure 0", code: 10089, userInfo: nil))
}
let code = response.statusCode
if code == 404 {
return .failure(NSError.init(domain: "custom failure 1", code: 100800, userInfo: nil))
}
return .success
}
validate
在得到 本次請求的響應(yīng)
之前被調(diào)用,在此處可以驗(yàn)證數(shù)據(jù)是否符合我們的業(yè)務(wù)邏輯,并向業(yè)務(wù)層返回自定義的錯(cuò)誤信息.
來看看源碼:
extension DataRequest {
@discardableResult
public func validate(_ validation: @escaping Validation) -> Self {
//創(chuàng)建一個(gè)閉包,在此閉包中調(diào)用 validation 參數(shù)閉包
let validationExecution: () -> Void = { [unowned self] in
if
let response = self.response,
self.delegate.error == nil,
case let .failure(error) = validation(self.request, response, self.delegate.data)
{
self.delegate.error = error
}
}
//添加驗(yàn)證閉包
validations.append(validationExecution)
return self
}
}
此方法保存了 validation
閉包. 并且把返回的自定義error
信息保存到 self.delegate.error
中 .
何時(shí)進(jìn)行驗(yàn)證?
extension SessionDelegate : URLSessionTaskDelegate {
open func urlSession(_ session: URLSession, task: URLSessionTask,
didCompleteWithError error: Error?) {
let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
guard let strongSelf = self else { return }
strongSelf.taskDidComplete?(session, task, error)
strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
var userInfo: [String: Any] = [Notification.Key.Task: task]
if let data = (strongSelf[task]?.delegate as? DataTaskDelegate)?.data {
userInfo[Notification.Key.ResponseData] = data
}
NotificationCenter.default.post(
name: Notification.Name.Task.DidComplete,
object: strongSelf,
userInfo: userInfo
)
strongSelf[task] = nil
}
///省略...
// Run all validations on the request before checking if an error occurred
request.validations.forEach { $0() }
// Determine whether an error has occurred
var error: Error? = error
if request.delegate.error != nil {
error = request.delegate.error
}
///省略...
completeTask(session, task, error)
}
}
是在每一次 DataTask
請求數(shù)據(jù)完成時(shí)調(diào)用保存的 validation
閉包, 并且把自定義的error
信息放到 dataResponse
中最后返回給外界.