Alamofire 是什麼?
- 在功能角度,
Alamofire
是一個http請求框架。使用它可以很方便的處理http請求(請求資料,下載,上傳)。 - 在程式碼實現角度,
Alamofire
是對NSURLSession
的封裝。 - 在語言角度可以理解為
Alamofire
是AFNetworking
的Swift實現(它們出自同一作者)。
本博文適用物件
在這篇博文我 不會闡述Alamofire的
使用方法 , 而是介紹Alamofire
的設計思想和組織結構 。希望這篇博文能夠拋磚引玉,助你輕鬆的理解Alamofire的實現細節。
NSURLSession概述
Alamofire
是對NSURLSession
的封裝, 在分析Alamofire
前,我們先簡單看下NSURLSession
的api。
Creating a Session
1 2 3 |
//兩個初始化方法 init(configuration: URLSessionConfiguration) init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate?, delegateQueue queue: OperationQueue?) |
Configuring a Session
1 2 3 4 |
//配置資訊,必須在init方法中指定。在指定後不能修改 var configuration: URLSessionConfiguration { get } var delegate: URLSessionDelegate? { get } var delegateQueue: OperationQueue { get } |
Adding Tasks
- Adding Data Tasks to a Session
1234567//根據url建立Data Task,然後呼叫URLSessionDataTask物件的resume方法來開始。當接受到response時會呼叫session’s delegate。func dataTask(with url: URL) -> URLSessionDataTask//與上面方法的唯一不同是task結束後呼叫completionHandler閉包,不再呼叫session’s delegate。後面還有幾個帶有completionHandler的方法,就不在說明。func dataTask(with url: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask//根據URLRequest物件建立Data Task。URLRequest含有更豐富的配置資訊,比如cachePolicy,timeoutInterval等。func dataTask(with request: URLRequest) -> URLSessionDataTaskfunc dataTask(with request: URLRequest, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask - Adding Download Tasks to a Session
123456func downloadTask(with url: URL) -> URLSessionDownloadTaskfunc downloadTask(with url: URL, completionHandler: (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTaskfunc downloadTask(with request: URLRequest) -> URLSessionDownloadTaskfunc downloadTask(with request: URLRequest, completionHandler: (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTaskfunc downloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTaskfunc downloadTask(withResumeData resumeData: Data, completionHandler: (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask - Adding Upload Tasks to a Session
12345func uploadTask(with request: URLRequest, from bodyData: Data) -> URLSessionUploadTaskfunc uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTaskfunc uploadTask(with request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTaskfunc uploadTask(with request: URLRequest, fromFile fileURL: URL, completionHandler: (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTaskfunc uploadTask(withStreamedRequest request: URLRequest) -> URLSessionUploadTask - Adding Stream Tasks to a Session
12func streamTask(withHostName hostname: String, port: Int) -> URLSessionStreamTaskfunc streamTask(with service: NetService) -> URLSessionStreamTask框架核心:Manager
Alamofire.swift檔案中定義了一組方法以方便我們進行 request, download,upload操作(差不多解決了90%的需求吧)。 這些方法只是為了方便使用,真正做事情的是類
Manager
。Manager
才是框架的核心。框架結構如下:
- session: 所有的task都是由這個session建立的,也就是說對session 的配置資訊作用於所有的task。
- delegate: session的代理由專門的類
SessionDelegate
負責。SessionDelegate
實現了NSURLSessionDelegate
,NSURLSessionTaskDelegate
,NSURLSessionDataDelegate
,
NSURLSessionDownloadDelegate
協議,並實現了這些協議的所有方法。SessionDelegate
就是回撥事件的樞紐中心,完成回撥的統一處理和散發。 - backgroundCompletionHandler:當需要執行後臺操作任務時使用。具體內容:NSURLSession使用說明及後臺工作流程分析。
- AddingTasksMethod:這是一組建立Task的方法。上面 NSURLSession概述 中提到一組 Adding Tasks 方法, 框架對這些方法進行了封裝,我們統一稱這組方法為AddingTasksMethod。 在後面的 建立Task 部分再細說。
- startRequestsImmediately:當建立task後,需要呼叫這個task的
resume
方法才會開始執行。startRequestsImmediately=true時, 建立好task就會resume。
看下初始化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//使用單例模式建立唯一的Manger。 public static let sharedInstance: Manager = { let configuration =NSURLSessionConfiguration.defaultSessionConfiguration() //defaultHTTPHeaders, 定義http頭。內容包括:Accept-Encoding,Accept-Language,User-Agent configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders return Manager(configuration: configuration) }() //建立session, delegate,serverTrustPolicyManager。 public init( configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration(),delegate: SessionDelegate = SessionDelegate(),serverTrustPolicyManager: ServerTrustPolicyManager? = nil){ self.delegate = delegate self.session = NSURLSession(configuration: configuration, delegate: delegate, delegateQueue: nil) //一個session對應一個serverTrustPolicyManager。當使用https需要驗證伺服器證書時可以通過serverTrustPolicyManager來配置驗證策略。例如當伺服器使用自定義證書時就可以使用 serverTrustPolicyManager來滿足需求。https涉及內容太多,這裡就不展開細說了。 commonInit(serverTrustPolicyManager: serverTrustPolicyManager) } |
至此我們已經建立好了session和它的delegate。呼叫流程大致這個樣子: 通過Manager
建立session
和SessionDelegate
物件——>使用AddingTasksMethod建立task—–>task呼叫resume()開始執行——>呼叫SessionDelegate
中實現的代理方法。
使用閉包過載代理方法
為了使用框架的靈活性,SessionDelegate
為每一個delegate方法宣告瞭一個對應的閉包。這樣你就可以很方便的指定如何處理回撥。
例如:
1 2 3 4 5 6 7 8 |
//當需要認證時會呼叫這個回撥 public func URLSession(session: NSURLSession,didReceiveChallenge challenge: NSURLAuthenticationChallenge,completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)){ guard sessionDidReceiveChallengeWithCompletion == nil else { sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler) return } //如果沒有對閉包賦值,執行框架中的預設操作 } |
建立Task
框架中對 NSURLSession概述 中的 Adding Tasks 方法進行了封裝,組成了前面提到的AddingTasksMethod。
request
通過呼叫下面的方法,建立NSURLSessionDataTask
物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public protocol URLRequestConvertible { var URLRequest: NSMutableURLRequest { get } } extension NSURLRequest: URLRequestConvertible { public var URLRequest: NSMutableURLRequest { return self.mutableCopy() as! NSMutableURLRequest } } public func request(URLRequest: URLRequestConvertible) -> Request { var dataTask: NSURLSessionDataTask! dispatch_sync(queue) {dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest) } //其他程式碼... } |
在上面程式碼中,URLRequest.URLRequest
就是一個NSMutableURLRequest
物件。我們使用NSMutableURLRequest
建立了一個NSURLSessionDataTask
。queue
是序列佇列,在序列佇列 queue
中執行同步方法能夠確保建立task時的執行緒安全。對執行緒有疑惑的可以看這裡GCD 深入理解:第一部分。
download
通過呼叫下面的方法,建立URLSessionDownloadTask
物件。 下載包括兩種方式:直接下載和斷點續傳。download方法通過enum
對不同型別進行區分。destination
引數是一個閉包,用於在下載結束後確定將下載的檔案儲存在什麼路徑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private enum Downloadable { case Request(NSURLRequest) case ResumeData(NSData) } private func download(downloadable: Downloadable, destination: Request.DownloadFileDestination) -> Request { var downloadTask: NSURLSessionDownloadTask! switch downloadable { case .Request(let request): dispatch_sync(queue) { downloadTask = self.session.downloadTaskWithRequest(request) } case .ResumeData(let resumeData): dispatch_sync(queue) { downloadTask = self.session.downloadTaskWithResumeData(resumeData) } } } |
upload
通過呼叫下面的方法,建立URLSessionUploadTask
物件。 upload分為三種:上傳NSData
,上傳NSURL
指定的檔案,還有NSInputStream
。在upload方法中使用列舉來區分不同的上傳內容。 邏輯很簡單,不在贅述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private enum Uploadable { case Data(NSURLRequest, NSData) case File(NSURLRequest, NSURL) case Stream(NSURLRequest, NSInputStream) } private func upload(uploadable: Uploadable) -> Request { var uploadTask: NSURLSessionUploadTask! var HTTPBodyStream: NSInputStream? switch uploadable { case .Data(let request, let data): dispatch_sync(queue) { uploadTask = self.session.uploadTaskWithRequest(request, fromData: data) } case .File(let request, let fileURL): dispatch_sync(queue) { uploadTask = self.session.uploadTaskWithRequest(request, fromFile: fileURL) } case .Stream(let request, let stream): dispatch_sync(queue) { uploadTask = self.session.uploadTaskWithStreamedRequest(request) } HTTPBodyStream = stream } } |
stream
通過下面的方法生成NSURLSessionStreamTask
物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
private enum Streamable { case Stream(String, Int) case NetService(NSNetService) } private func stream(streamable: Streamable) -> Request { var streamTask: NSURLSessionStreamTask! switch streamable { case .Stream(let hostName, let port): dispatch_sync(queue) { streamTask = self.session.streamTaskWithHostName(hostName, port: port) } case .NetService(let netService): dispatch_sync(queue) { streamTask = self.session.streamTaskWithNetService(netService) } } } |
重構,各司其職
存在的問題
到此總算把各種Task
建立出來了。呼叫Task
的resume方法就可以開始執行任務。從伺服器返回資料後就會呼叫SessionDelegate
的代理方法。當同時執行多個Task
時,我的天,發生了什麼。。。 下面就拿NSURLSessionDataTask
舉例。當多個task同時執行時,他們會交替頻繁的呼叫SessionDelegate
代理方法。
現在我有下面幾個需求:
- 顯示出每個task的執行進度
- task1得到全部資料後解析成jsonObject, task2得到全部資料後解析成string, task3得到全部資料後解析成propertyList。。。。
建立TaskDelegate
及子類,為每個task建立專有的delegate物件
要滿足上面的需求,我們必須為每一個task建立一個代理物件,這個代理物件只處理這個task的代理回撥。TaskDelegate
及子類就是用於做這件事情的。
執行步驟是這個樣子的:
- 我們建立了一個建立
NSURLSessionDataTask
物件dataTask1; - 建立dataTask1對應的
DataTaskDelegate
物件dataTask1Delegate; - dataTask1獲取到資料後呼叫
SessionDelegate
的public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)
- 在
SessionDelegate
的public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)
方法中呼叫DataTaskDelegate
的public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData)
方法。
使用Request
封裝建立TaskDelegate的過程
為了方便建立TaskDelegate
物件,框架有一個Request
類。Request
所做的事情就是為每個Task生成對應的TaskDelegate
物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Request { public let delegate: TaskDelegate public var task: NSURLSessionTask { return delegate.task } public let session: NSURLSession public var request: NSURLRequest? { return task.originalRequest } public var response: NSHTTPURLResponse? { return task.response as? NSHTTPURLResponse } public var progress: NSProgress { return delegate.progress } init(session: NSURLSession, task: NSURLSessionTask) { self.session = session switch task { case is NSURLSessionUploadTask: delegate = UploadTaskDelegate(task: task) case is NSURLSessionDataTask: delegate = DataTaskDelegate(task: task) case is NSURLSessionDownloadTask: delegate = DownloadTaskDelegate(task: task) default: delegate = TaskDelegate(task: task) } delegate.queue.addOperationWithBlock { self.endTime = CFAbsoluteTimeGetCurrent() } } } |
修改建立Task方法
現在我們需要修改建立Task的方法如下(以request為例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public func request(URLRequest: URLRequestConvertible) -> Request { var dataTask: NSURLSessionDataTask! dispatch_sync(queue) { dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest) } // 建立task獨有的代理物件 let request = Request(session: session, task: dataTask) //下面這句話就是將生成的代理物件儲存在 SessionDelegate的一個字典中。這樣在回撥時就可以根據task獲取這個task對應的代理物件。 delegate[request.delegate.task] = request.delegate if startRequestsImmediately { request.resume() } return request } |
設定好了之後, 在回撥中根據task取出這個task對應的代理物件, 然後執行對應的方法就ok了。
處理響應結果
在TaskDelegate
類中有序列佇列queue:NSOperationQueue,並設定這個佇列的queue.suspended = true(新增block不會執行,直到suspended=false) 你可以這個佇列中新增要執行的block。在func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)
代理方法中如果error=nil,設定queue.suspended = false。 處理相應結果大致就是這個原理。
寫在最後
- 為了便於理解,在學習框架時畫的腦圖,相對於文字,腦圖結構更加清晰明瞭。Alamofire框架腦圖
- 雖然寫了這麼多但還是感覺很多點沒有提到。比如說用於對請求引數編碼的
ParameterEncoding
,用於對請求響應資料序列化的ResponseSerialization
,用於https驗證伺服器證書的ServerTrustPolicy
, upload操作中對MultipartFormData
的具體實現。這些程式碼質量都很高,值得推薦。 - 能力有限,難免出現錯誤。如發現問題希望不吝指教。
- 寫這麼多著實佔用不少時間,如果對你有幫助,那就給個喜歡吧。