本篇介紹Task代理(TaskDelegate.swift)html
我相信可能有80%的同窗使用AFNetworking
或者Alamofire
處理網絡事件,而且這兩個框架都提供了豐富的功能,我也相信不少人都作了二次封裝,但事實上,這個二次封裝卻又異常簡單或者是簡陋。這篇文章的內容是Task代理,是一篇很獨立的文章,你們能夠經過這篇文章瞭解iOS中網絡開發是怎麼一回事。git
那麼一條最普通的網絡請求,到底是怎樣的一個過程?首先咱們根據一個URL和若干個參數生成Request,而後根據Request生成一個會話Session,再根據這個Session生成Task,咱們開啓Task就完成了這個請求。固然這一過程之中還會包含重定向,數據上傳,挑戰,證書等等一系列的配置信息。github
咱們再聊聊代理的問題,無論是在網絡請求中,仍是再其餘的地方,代理都相似於一個管理員的身份。這在業務架構中是一個很好的主意。假如我把代理想象成一我的,那麼這我的的工做是什麼呢?swift
提供處理業務的方法,這每每被用於事件的傳遞。這是一種狹義上的代理,是一個協議。在開發中,無論是view仍是Controller,均可以利用代理傳遞事件。但咱們這裏說的不是協議,協議必需要求咱們實現它的屬性和方法,這對上邊提到的‘人’是不友好的。在真實的理想的場景中,我和代理的交互只有兩種狀況:api
若是對上邊的內容不太明白,只須要明白,有的代理是一個協議,有的代理是一個'人',在某些讓你頭疼的複雜的業務中,用代理'人'去處理。我在想是否是不叫代理,叫Manager更合適。瀏覽器
URLSessionTask是對task最基本的封裝。按照請求的目的和響應的結果能夠分爲:緩存
上邊圖中表示了一種繼承關係,與之相對應的代理以下圖:服務器
我會在下邊詳細講解這些代理方法。網絡
TaskDelegate位於繼承鏈的最底層,所以它提供了一些最基礎的東西,這些東西也是其餘Delegate共享的,咱們先看看屬性:session
// MARK: Properties /// The serial operation queue used to execute all operations after the task completes. open let queue: OperationQueue /// The data returned by the server. public var data: Data? { return nil } /// The error generated throughout the lifecyle of the task. public var error: Error? var task: URLSessionTask? { didSet { reset() } } var initialResponseTime: CFAbsoluteTime? var credential: URLCredential? var metrics: AnyObject? // URLSessionTaskMetrics
咱們來分析下這些屬性:
queue: OperationQueue
很明顯這是一個隊列,隊列中能夠添加不少operation。隊列是能夠被暫停的,經過把isSuspended
設置爲true就可讓隊列中的全部operation暫停,直到isSuspended
設置爲false後,operation纔會開始執行。在Alamofire中,放入該隊列的operation有一下幾種狀況:
isSuspended
設置爲falsedata: Data?
表示服務器返回的Data,這個可能爲空error: Error?
表示該代理生命週期內有可能出現的錯誤,這一點很重要,咱們在寫一個代理或者manager的時候,能夠添加這麼一個錯誤屬性,專門抓取生命週期內的錯誤。task: URLSessionTask?
表示一個task,對於本代理而言,task是很重要的一個屬性。initialResponseTime: CFAbsoluteTime?
當task是URLSessionDataTask時,表示接收到數據的時間;當task是URLSessionDownloadTask時,表示開始寫數據的時間;當task是URLSessionUploadTask時,表示上傳數據的時間;credential: URLCredential?
表示證書,若是給該代理設置了這個屬性,在它裏邊的證書驗證方法中會備用到metrics: AnyObject?
apple提供了一個統計task信息的類URLSessionTaskMetrics
,能夠統計跟task相關的一些信息,包括和task相關的全部事務,task的開始和結束時間,task的重定向的次數。
上邊的這些屬性中,能夠留意一下queue的使用方法,儘可能爲設計的代理添加錯誤處理機制。
Alamofire使用相似Properties,Lifecycle等等關鍵字來分割文件的,看完了上邊的屬性,咱們在看看Lifecycle。
// MARK: Lifecycle init(task: URLSessionTask?) { self.task = task self.queue = { let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.isSuspended = true operationQueue.qualityOfService = .utility return operationQueue }() } func reset() { error = nil initialResponseTime = nil }
reset函數把error和initialResponseTime都置爲nil,這個沒什麼好說的,在init函數中隊列的建立頗有意思。在swift中,若是咱們要建立一個對象,無論是view仍是別的,均可以採用這樣的方式:建立一個函數,而後馬上調用,這很像JavaScript的用法。
lazy var label: UILabel = { let view = UILabel() view.backgroundColor = .clear view.textAlignment = .center view.textColor = .white view.font = .boldSystemFont(ofSize: 18) self.contentView.addSubview(view) return view }()
operationQueue.isSuspended = true
能夠保證隊列中的operation都是暫停狀態,正常狀況下,operation在被加入到隊列中後,會盡快執行。
在swift中函數是一等公民,能夠當參數和返回值來使用。同oc的block同樣,咱們能夠把他們當成一個屬性,目的是提早告訴代理當遇到指定的事件時應該怎麼作?在YYModel中有一小段代碼就是關於Block當返回值的妙用。咱們看看TaskDelegate下的四個相關函數:
// MARK: URLSessionTaskDelegate var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
咱們先看第一個函數,這個函數對應的代理方法以下:
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) func urlSession( _ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { var redirectRequest: URLRequest? = request if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) } completionHandler(redirectRequest) }
上邊的函數處理的問題是請求重定向問題,咱們大概講一下重定向是怎麼一回事:Web服務器有時會返回重定向響應而不是成功的報文。Web服務器能夠將瀏覽器重定向到其餘地方來執行請求。重定向響應由返回碼3XX說明。Location響應首部包含了內容的新地址或優選地址的URI重定向可用於下列狀況:
上邊的重定向函數要求返回一個redirectRequest,就是重定向的Request,Alamofire的處理方法就是,若是給代理的重定向處理函數賦值了,就返回代理函數的返回值,不然返回服務器返回的Request。
第二個函數用於處理驗證相關的事務。咱們先講講disposition
,他的類型是URLSession.AuthChallengeDisposition
,其實就是一個枚舉:
@available(iOS 7.0, *) public enum AuthChallengeDisposition : Int { case useCredential case performDefaultHandling case cancelAuthenticationChallenge case rejectProtectionSpace }
這個受權配置一共有四個選項:
useCredential
表示使用證書performDefaultHandling
表示採用默認的方式,這個方式跟服務器返回authenticationMethod有很大關係,我簡單列一些經常使用的method
NSURLAuthenticationMethodHTTPBasic
基本認證NSURLAuthenticationMethodHTTPDigest
摘要認證NSURLAuthenticationMethodClientCertificate
客戶端證書認證NSURLAuthenticationMethodServerTrust
表示返回的證書要使用serverTrust建立cancelAuthenticationChallenge
表示取消認證rejectProtectionSpace
表示拒絕認證
對於驗證而言,有三種方式:
咱們先給出Alamofire中的函數,而後在分析:
@objc(URLSession:task:didReceiveChallenge:completionHandler:) func urlSession( _ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling var credential: URLCredential? if let taskDidReceiveChallenge = taskDidReceiveChallenge { (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { let host = challenge.protectionSpace.host if let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host), let serverTrust = challenge.protectionSpace.serverTrust { if serverTrustPolicy.evaluate(serverTrust, forHost: host) { disposition = .useCredential credential = URLCredential(trust: serverTrust) } else { disposition = .cancelAuthenticationChallenge } } } else { if challenge.previousFailureCount > 0 { disposition = .rejectProtectionSpace } else { credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) if credential != nil { disposition = .useCredential } } } completionHandler(disposition, credential) }
若是服務器須要驗證客戶端的,咱們只須要給TaskDelegate的 var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
賦值就好了。
這裏有一個很重要的問題,HTTPS並不會觸發上邊的回調函數,緣由就是NSURLSession內部有一個根證書,內部會跟服務器的證書進行驗證,若是服務器的證書是證書機構頒發的話,就能夠順利經過驗證,不然會報錯。
另外一個很重要的問題是,上邊的方法在何種狀況下觸發,按照apple的說法URL Session Programming Guide,當服務器響應頭中包含WWW-Authenticate
或者使用 proxy authenticatio
n TLS trust validation
時,上邊的方法就會被觸發,其餘狀況下都不會觸發。
Alamofire是雙向驗證的
雙向驗證的過程:
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
時,服務器提供了一個服務器信任的證書,但這個證書也僅僅是服務器本身信任的,攻擊者徹底能夠提供一個證書騙過客戶端,基於這個問題,客戶端須要驗證服務端的證書ServerTrustPolicy.swift
這個類來驗證證書,大概過程就是拿本地的證書跟服務端返回的證書進行對比,若是客戶端存在相同的證書就表示經過,這個過程我會在ServerTrustPolicy.swift
那篇文章中給出詳細的解答所以,客戶端和服務端要創建SSL只須要兩步就好了:
WWW-Authenticate
響應頭,並返回本身信任證書第三個函數:
///This delegate method is called under two circumstances: ///To provide the initial request body stream if the task was created with uploadTask(withStreamedRequest:) ///To provide a replacement request body stream if the task needs to resend a request that has a body stream because of an authentication challenge or other recoverable server error. ///Note ///You do not need to implement this if your code provides the request body using a file URL or an NSData object. @objc(URLSession:task:needNewBodyStream:) func urlSession( _ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { var bodyStream: InputStream? if let taskNeedNewBodyStream = taskNeedNewBodyStream { bodyStream = taskNeedNewBodyStream(session, task) } completionHandler(bodyStream) }
當給task的Request提供一個body stream時纔會調用,咱們不須要關心這個方法,即便咱們經過fileURL或者NSData上傳數據時,該函數也不會被調用,使用場景不多。
第四個函數:
@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 } }
該函數在請求完成後被調用,值得注意的是error不爲nil的狀況,除了給自身的error屬性賦值外,針對下載任務作了特殊處理,就是把當前已經下載的數據保存在downloadDelegate.resumeData中,有點像斷點下載。
DataTaskDelegate
繼承自TaskDelegate
,實現了URLSessionDataDelegate
協議。所以下邊咱們也會講解URLSessionDataDelegate
協議的方法。咱們仍是先看這裏邊的屬性:
// MARK: Properties var dataTask: URLSessionDataTask { return task as! URLSessionDataTask } override var data: Data? { if dataStream != nil { return nil } else { return mutableData } } var progress: Progress var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? var dataStream: ((_ data: Data) -> Void)? private var totalBytesReceived: Int64 = 0 private var mutableData: Data private var expectedContentLength: Int64?
咱們對這些屬性給出必定的解釋:
dataTask: URLSessionDataTask
DataTaskDelegate管理URLSessionDataTaskdata: Data?
一樣是返回Data,但這裏有一點不一樣,若是定義了dataStream
方法的話,這個data返回爲nilprogress: Progress
進度progressHandler
這不是函數,是一個元組,何時調用,在下邊的方法中給出說明dataStream
自定義的數據處理函數totalBytesReceived
已經接受的數據mutableData
保存數據的容器expectedContentLength
須要接受的數據的總大小DataTaskDelegate的生命週期:
// MARK: Lifecycle override init(task: URLSessionTask?) { mutableData = Data() progress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() progress = Progress(totalUnitCount: 0) totalBytesReceived = 0 mutableData = Data() expectedContentLength = nil }
這些沒什麼好說的,咱們在看看有哪些函數:
// MARK: URLSessionDataDelegate var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
URLSessionDataDelegate有四個函數,咱們先看第一個函數:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { var disposition: URLSession.ResponseDisposition = .allow expectedContentLength = response.expectedContentLength if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { disposition = dataTaskDidReceiveResponse(session, dataTask, response) } completionHandler(disposition) }
當收到服務端的響應後,該方法被觸發。在這個函數中,咱們可以獲取到和數據相關的一些參數,你們能夠想象成響應頭。Alamofire中給出了一個函數dataTaskDidReceiveResponse,咱們能夠利用這個函數控制是否是要繼續獲取數據,默認是.allow。
咱們看第二個函數:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) { dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask) }
在上邊的disposition配置中,disposition的類型是URLSession.ResponseDisposition,咱們看看這個枚舉:
public enum ResponseDisposition : Int { case cancel /* Cancel the load, this is the same as -[task cancel] */ case allow /* Allow the load to continue */ case becomeDownload /* Turn this request into a download */ @available(iOS 9.0, *) case becomeStream /* Turn this task into a stream task */ }
當選擇了becomeDownload
後,就會觸發上邊的第二個函數,在函數中會提供一個新的downloadTask。這就給咱們一個把dataTask轉換成downloadTask的機會
那麼咱們把dataTask轉換成downloadTask究竟有什麼用呢?我想到了一個使用場景,假如給定一個URL,返回的數據是Data,其實我想把這些數據下載下來,那麼就可使用上邊的這種技術了。舉個例子,https://baidu.com
打開這個url會直接顯示網頁,使用上邊的技術,打開這個url會直接下載網頁。我並無驗證上邊的想法。
咱們繼續看第三個函數:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let dataTaskDidReceiveData = dataTaskDidReceiveData { dataTaskDidReceiveData(session, dataTask, data) } else { if let dataStream = dataStream { dataStream(data) } else { mutableData.append(data) } let bytesReceived = Int64(data.count) totalBytesReceived += bytesReceived let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown progress.totalUnitCount = totalBytesExpected progress.completedUnitCount = totalBytesReceived if let progressHandler = progressHandler { progressHandler.queue.async { progressHandler.closure(self.progress) } } } }
這個方法算是核心方法,我在MCDownloadManager中實現下載的核心方法就是這個方法,不一樣之處是,Alamofire把數據放入對象中,而我把數據寫入本地文件中。對這個函數內部就不作解釋了,主要就是對自定義函數和進度的一些處理。
咱們看第四個函數:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { var cachedResponse: CachedURLResponse? = proposedResponse if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse) } completionHandler(cachedResponse) }
其實,每每這樣的函數纔是咱們應該注意的,最多見的接受響應,處理數據,請求完成都是咱們很熟悉的方法,所以更應該多多注意這些不太熟悉的方法。
該函數用於處理是否須要緩存響應,Alamofire默認是緩存這些response的,可是每次發請求,它不會再緩存中讀取。
DownloadTaskDelegate
繼承自TaskDelegate
,實現了URLSessionDownloadDelegate
協議。所以下邊咱們也會講解URLSessionDownloadDelegate
協議的方法。咱們仍是先看這裏邊的屬性:
// MARK: Properties var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask } var progress: Progress var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? var resumeData: Data? override var data: Data? { return resumeData } var destination: DownloadRequest.DownloadFileDestination? var temporaryURL: URL? var destinationURL: URL? var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
這些屬性中有和上邊介紹的屬性重複的部分,咱們只對不重複的部分給出說明:
downloadTask
和URLSessionDownloadDelegate相對應的URLSessionDownloadTaskresumeData
在上邊咱們提到過,當請求完成後,若是error不爲nil,若是是DownloadTaskDelegate,就會給這個屬性賦值data
返回resumeDatadestination
經過這個函數能夠自定義文件保存目錄和保存方式,這個保存方式分兩種,爲URl建立文件夾,刪除已經下載且存在的文件,這個會在後續的文章中提到temporaryURL
臨時的URLdestinationURL
數據存儲URLfileURL
返回文件的路徑,若是destination不爲nil,就返回destinationURL,不然返回temporaryURL生命週期:
// MARK: Lifecycle override init(task: URLSessionTask?) { progress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() progress = Progress(totalUnitCount: 0) resumeData = nil }
和下載相關的代理函數有三個:
// MARK: URLSessionDownloadDelegate var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)? var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
咱們先看看第一個函數:
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 /// 說明在編碼過程當中,對於存在可能出現錯誤的地方,必定要作error處理 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 } }
對於這樣的代理方法,咱們首先要作的就是弄明白在什麼狀況下它會被觸發。當數據下載完成後,該函數被觸發。系統會把數據下載到一個臨時的locationURL的地方,咱們就是經過這個URL拿到數據的。上邊函數內的代碼主要是把數據複製到目標路徑中。
可是我有一個疑問?按照apple文檔的內容:If you choose to open the file for reading, you should do the actual reading in another thread to avoid blocking the delegate queue.
應該在另外一個線程來讀取數據,這樣纔不會阻塞當前的代理線程,不知道有什麼影響?
咱們來看第二個函數:
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let downloadTaskDidWriteData = downloadTaskDidWriteData { downloadTaskDidWriteData( session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite ) } else { progress.totalUnitCount = totalBytesExpectedToWrite progress.completedUnitCount = totalBytesWritten if let progressHandler = progressHandler { progressHandler.queue.async { progressHandler.closure(self.progress) } } } }
該代理方法在數據下載過程當中被觸發,主要的做用就是提供下載進度。這個比較簡單,咱們看看第三個函數:
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) } else { progress.totalUnitCount = expectedTotalBytes progress.completedUnitCount = fileOffset } }
是這樣的,若是一個下載的task是能夠恢復的,那麼當下載被取消或者失敗後,系統會返回一個resumeData對象,這個對象包含了一些跟這個下載task相關的一些信息,有了它就能從新建立下載task,建立方法有兩個:downloadTask(withResumeData:)
和downloadTask(withResumeData:completionHandler:)
,當task開始後,上邊的代理方法就會被觸發。
UploadTaskDelegate
繼承自DataTaskDelegate
。對於上傳數據來講最麻煩的就是多表單數據的上傳,這個咱們會在後續的MultipartFormData.swift
給出詳細的解釋。
咱們先看看它的屬性有哪些?
// MARK: Properties var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask } var uploadProgress: Progress var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
這些和上邊出現的內容有重疊,在這裏就很少作解釋了,咱們再看看生命週期:
// MARK: Lifecycle override init(task: URLSessionTask?) { uploadProgress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() uploadProgress = Progress(totalUnitCount: 0) }
也沒什麼好說的,再看看函數:
// MARK: URLSessionTaskDelegate var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? func URLSession( _ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let taskDidSendBodyData = taskDidSendBodyData { taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } else { uploadProgress.totalUnitCount = totalBytesExpectedToSend uploadProgress.completedUnitCount = totalBytesSent if let uploadProgressHandler = uploadProgressHandler { uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) } } } }
該函數主要目的是提供上傳的進度,在Alamofire中,上傳數據用的是stream,這個會在後續文章中給出詳細的解釋。
我我的解讀源碼的方式可能比較特別,我喜歡把全部的代碼都寫到文章之中。由於人的記憶都是有問題的,好多東西當時記住了,過段時間就忘了,爲了方便往後查看這些筆記,我以爲仍是把代碼都弄上來比較好。
同一段代碼,不一樣的人看會有不一樣的想法,這些解讀也能夠給別人一些參考。我如今愈來愈以爲代碼的設計很重要了。
因爲知識水平有限,若有錯誤,還望指出
Alamofire源碼解讀系列(一)之概述和使用 簡書-----博客園
Alamofire源碼解讀系列(二)之錯誤處理(AFError) 簡書-----博客園
Alamofire源碼解讀系列(三)之通知處理(Notification) 簡書-----博客園