Alamofire
應該是用Swift做iOS開發里最常用到的三方框架了。我們在開發過程中常會遇到的網絡請求,如:向服務端請求json數據,上傳圖片,下載文件等,都可以直接調用Alamofire或者二次封裝它再進行調用。這里假定你已經使用過Alamofire,那么本篇文章會對Alamofire的部分源碼選擇性的介紹。當然,這里并不會貼上源碼里的大段代碼,而是通過一個例子來進入到Alamofire中的各個模塊,讓讀者更好理解。
我們先來看看一個Alamofire的調用例子:
let url = "http://suggest.taobao.com/sug" //淘寶的一個搜索api
let parameters: [String: Any] = [ //對`襪子`進行搜索
"code" : "utf-8",
"q" : "襪子"
]
`Alamofire`.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil)
.validate(statusCode: [200])
.responseData(queue: DispatchQueue.global(), completionHandler: { (responseData) in
switch responseData.result {
case .success(let data):
guard let jsonString = String(data: data, encoding: .utf8) else { return }
print(jsonString)
case .failure(let error):
print(error)
}
})
// `Alamofire`兩邊的單引號是為了markdown語法的顯示更清晰,請忽略掉。
這個方法是Alamofire
的基本用法。
我們通過Alamofire的request
開始調用,
1.傳入url
2.使用get
方法
3.傳入parameters
,并在encoding
中定義了參數的編碼方式
4.因為是一個公共api,這里并不需要headers
,headers傳nil
5.在validate
中傳入需要驗證的statusCode的數組
6.在responseData
方法里傳入全局隊列和回調的處理閉包completionHandler
7.在閉包里對返回的數據里進行判斷,若返回成功,打印jsonString,若失敗則打印錯誤。
我們從前往后一點點來看。
(1)request方法做了什么事情?
我們點擊request進入Alamofire.swift從最上面看起(這里建議同步點開Alamofire里的源文件,便于對比)
-
URLConvertible
協議 的定義。我們在例子里傳入的url是String類型,也可以將它轉成URL類型再傳入,結果都正確。
就是因為String和URL都遵循URLConvertible協議,通過實現協議里的
func asURL() throws -> URL
方法,從String,URL或URLComponents類型里轉換為URL,若失敗則拋出定位為AFError
的錯誤
往下看,是協議
URLRequestConvertible
的定義,它是負責URLRequest
的轉換,和上面類似,就不多介紹了。再望下看,就是Alamofire對外公開的常用接口了。包括request,download,upload等方法的定義。而它們實際上都是調用了
SessionManager.default.request(...) -> Request //傳入參數省略
SessionManager
是Alamofire中最外層用于發起網絡請求的單例類,它會在init方法中定義實際的請求會話URLSession。而請求實際的調用者是URLSession。
結論是通過request
方法,傳入所需參數,發起請求,并返回Request
的子類
如:DataRequest
,DownloadRequest
等,而DataRequest又繼承于Request.
(2)返回給我們Request的子類有什么用呢?
我們進入 Request.swift 來看看Request類型的實現。
- 定義了
RequestTask
,為請求任務分類 - 持有了
task
(請求任務),session
(請求對話),request
(請求),response
(響應)等 - 定義了我們會常涉及到的任務管理方法,包含resume(激活),suspend(暫停),cancel(取消),這里我們來看一下cancel的實現
/// 取消請求.
open func cancel() {
guard let task = task else { return }
task.cancel() //這里的task是request持有的URLSessionTask,實際上是調用它的取消方法
NotificationCenter.default.post( //當取消任務時,發送DidCancel的通知
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
如果一個頁面里的請求未結束就關閉頁面,我們想要結束這次請求時,
可以在Alamofire請求的末尾調用cancel()
方法來取消這次請求,它實際上是調用真正的網絡請求URLSessionTask的cancel()方法。而Request
的其他實現以及它的子類DataRequest
,DownloadRequest
這里也不詳細介紹了。這里也可以順便看看 Notification.swift ,了解一下Alamofire封裝了哪些通知
結論是:返回給我們的Request,可以讓我們控制請求的暫停,恢復,取消等。
(3)我們在例子中向encoding傳入URLEncoding.default
有什么用?
我們進入 ParameterEncoding.swift 看看它的實現
一來就看到了枚舉類型HTTPMethod
的實現,在這個文件下定義.get
,.post
,.put
方法等類型,證明ParameterEncoding(即參數編碼)和具體的請求方式是有聯系的。
我們看到ParameterEncoding
是一個協議,并且只有一個方法
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
//此方法是為了完成對傳入的parameters的編碼工作
我們常會用到的URLEncoding
,JSONEncoding
,以及不常用到的PropertyListEncoding
都遵循這個協議。
這里截取URLEncoding中對該方法實現中會調用的一個方法
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: "&")
}
//本方法以及它的調用處可以看到URLEncoding方式是將parameters通過添加 & ,= 的方式拼接到url身后
而如果JSONEncoding方式是將parameters轉化為二進制放入httpBody里。
因此也很好理解,為什么get方法的安全性不如post方法,因為get將請求參數暴露在外,而post在內部。
因此,我們將調用例子里的url變為
let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
//%E8%A2%9C%E5%AD%90由對`襪子`urlencode轉化而來。
并且向parameter傳nil,依然會得到同樣的返回。因為現在這個url就是之前請求時,Alamofire轉化之后的url。當然轉化的過程不僅僅止于此,這里只說了其中的代表部分
結論是encoding: 是對參數進行編碼,如果傳入URLEncoding,會將參數拼接進url;如果傳入JSONEncoding,則將參數轉為二進制放入httpbody(Propertylist是以plist方式編碼,我也很少用,就不介紹了)
(4)例子中傳入的headers
做了些什么?
public typealias HTTPHeaders = [String: String]
由HTTPHeaders的別名我們得知它的類型是[String: String]。
我們在Alamofire.swift里順藤摸瓜找到了我們傳的headers實際上是怎么處理的
extension URLRequest {
public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
let url = try url.asURL()
self.init(url: url)
httpMethod = method.rawValue
if let headers = headers {
for (headerField, headerValue) in headers {
setValue(headerValue, forHTTPHeaderField: headerField)
}
}
}
}
在URLRequest的擴展里,添加了它的init方法。在init方法里,遍歷了headers里的key和value,
并調用setValue: forHTTPHeaderField:
方法將這些key,value放入URLRequest的請求頭里
結論是:傳入的headers
是會將其中的key,value賦給URLRequest的實例,也就是我們常說的請求頭的設置
(5)例子中validate
做了些什么?
我們打開 Validation.swift 發現,validate是對Request及其子類的擴展中增加的方法,傳入一個Request并進行認證,再返回本身的類型,我們這里先看對statusCode的認證
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {}
那么到底是如何認證的呢?這里我們再進入它的內部實現,看到:
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) { //若validate傳入code包含response響應的code,則認證成功
return .success
} else { //若不包括,則返回失敗,拋出AFError
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
//acceptableStatusCodes為我們實際在validate中傳入的statusCode數組,在例子中為[200].
通過acceptableStatusCodes.contains(response.statusCode)
判斷請求返回的code是否是我們想要的。
在這里我們可以做個測試,將例子中的[200]變為[201],就會打印出拋出的錯誤。因為http的正確返回碼為200,我們傳入的數組不包含200,就會拋出錯誤。當然我們也可以在例子中不調用validate,這樣就不會有這些驗證。
這里只介紹了statusCode的認證,實際上validate里還有對contentType等的認證。
結論是:validate
會對http響應碼,或者響應內容類型等進行認證,若認證成功不影響請求,若認證失敗,會拋出錯誤。
(6)queue: DispatchQueue.global()
做了些什么?
我們在一步步點進去在 ResponseSerialization.swift 的reponse方法中發現
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
//若queue為空則使用主隊列處理返回數據
這行代碼是傳入queue的作用。
結論是:我們會把請求返回的處理放入queue:
所傳入的隊列中,若我們沒有向queue
傳入隊列,那么會默認把處理放入主隊列中。例子中我選擇將處理放入全局隊列中。
這里可以順便看一下 DispatchQueue+Alamofire.swift 文件,里面是對DispatchQueue增加屬性,獲取不同優先級
的隊列,并增加了一個名為after
的延遲調用方法。屬性和方法都沒有添加public關鍵字,那么它們只想在Alamofire內部調用,而不想暴露給我們。
(7)請求中的錯誤是如何分類的?
Alamofire里的錯誤都已經被定義到AFError
中,我們打開 AFError.swift ,可以看到
enum AFError { //這里為了看得方便,將AFError重新整理了下,源碼中的內容要更多,但類型是一樣的
case invalidURL //無效的URL
case parameterEncodingFailed //請求參數編碼失敗
case multipartEncodingFailed //多部分編碼失敗
case responseValidationFailed //響應驗證失敗
case responseSerializationFailed //響應序列化失敗
}
而AFError的類型中,除了invalidURL
的參數是遵循協議URLConvertible
,其余類型的參數又由枚舉類型組成。
這樣就將很多種的錯誤類型,包括在了這5種類型中。
往下看,還能看到很多內容,例如對localizedDescription
的實現,在請求拋出錯誤時,我們如果打印error.localizedDescription,看到的錯誤信息就是在這里定義的。
(8)還有哪些文件需要了解?
-
MultipartFormData.swift
中會看到我們在使用上傳功能時的編碼方式 -
NetworkReachabilityManager.swift
是Alamofire基于SCNetworkReachability
封裝的,用于監聽當前手機的網絡狀態的工具,我們可以直接用
NetworkReachabilityManager().networkReachabilityStatus
來獲取當前手機的網絡狀態
-ResponseSerialization.swift
里定義了對請求返回的請求方式
以上就是我們根據一個簡單的網絡請求,而定位到Alamofire的內部實現的過程。因為內容很多,并且為了博客的可讀性,這里只挑了一些基礎且常用的進行介紹。我們如果想深入了解Alamofire,并且學習它的設計思路,還是需要到源碼中一點點分析。
覺得有幫助的話,點個贊吧??~