簡單易懂的Alamofire使用及源碼分析

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,并且學習它的設計思路,還是需要到源碼中一點點分析。

覺得有幫助的話,點個贊吧??~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容