關於 Swift 中的 URLSession 和 Alamofire 原始碼閱讀

godiscoder發表於2017-03-07

URLSession/NSURLSession

URLSession相當於管理一個請求的類,它涉及到URL/URLRequest/URLSessionConfiguration/URLSessionTask。通過這幾個類的綜合使用,我們就可以很方便的建立請求

建立GET請求

let userTel = "13231852031"
let userPassword = "123456"
let defaultConfiguration = URLSessionConfiguration.default
let sessionWithoutADelegate = URLSession(configuration: defaultConfiguration)
if let url = URL(string: "http://127.0.0.1:8080/v1/login?usertel=\(userTel)&userpassword=\(userPassword)") {
    (sessionWithoutADelegate.dataTask(with: url) { (data, response, error) in
        if let error = error {
            print("Error: \(error)")
        } else if let response = response,
            let data = data,
            let string = String(data: data, encoding: .utf8) {
            print("Response: \(response)")
            print("DATA:\n\(string)\nEND DATA\n")
        }
    }).resume()
}複製程式碼

建立POST請求

let defaultConfiguration = URLSessionConfiguration.default
let sessionWithoutADelegate = URLSession(configuration: defaultConfiguration)
let paramer: [String: String] = ["userTel":"13231852031","userPassword":"123456"]

if let url = URL(string: "http://127.0.0.1:8080/v1/register") {
    var request = URLRequest.init(url: url)
    request.httpMethod = "POST"
    request.httpBody = try? JSONSerialization.data(withJSONObject: paramer, options: JSONSerialization.WritingOptions.prettyPrinted)

    sessionWithoutADelegate.dataTask(with: request, completionHandler: { (data, response, error) in
        if let error = error {
            print("Error:\(error)")
        }else if let response = response,
            let data = data,
            let string = String(data: data,encoding: .utf8){
            print("Response: \(response)\n")
            print("DATA:\(string)\n")
        }
    }).resume()
}複製程式碼

根據Alamofire的README讀其原始碼

Swift版本和Alamofire版本

  • Swift:3.0
  • Alamofire:4.4.0

Making a Request

Alamofire.request("https://httpbin.org/get")複製程式碼

首先,文件中給出上面的一個例子,我們可以看到很簡單就傳送了一個請求。接下來我們可以看一下Alamofire是如何實現的request,通過跳轉我們可以看到該方法是實現在Alamofire.swift檔案中:

@discardableResult
public func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    return SessionManager.default.request(
        url,
        method: method,
        parameters: parameters,
        encoding: encoding,
        headers: headers
    )
}複製程式碼

首先該方法通過URLConvertible協議來判斷URL的有效性(如未無效URL會丟擲Error),然後方法預設為GET,parameters引數預設為空,引數編碼預設為URLEncoding.default,header預設為空。在該方法中又呼叫了SessionManager,接下來我們再跳轉到SessionManager,在SessionManager中的request方法實現如下(由於方法較長下面程式碼只貼關鍵部分):

do {
    originalRequest = try URLRequest(url: url, method: method, headers: headers)
    let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
    return request(encodedURLRequest)
} catch {
    return request(originalRequest, failedWith: error)
}複製程式碼

在上述實現中我們可以看到,如果各項引數都沒問題的話,它會進入do程式碼塊中request方法.
do中的request方法實現如下:

do {
    originalRequest = try urlRequest.asURLRequest()
    let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
    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)
}複製程式碼

還是在判斷各項引數都正確後呼叫resume方法,在該方法中它會呼叫URLSessionTask的resume()來啟動該次請求。由此我們可以看出雖然我們只是呼叫這麼簡單的一句話,但是裡面還是由很多條件和邏輯的,只不過這一切Alamofire替我們做了。

上述的程式碼邏輯如果把所有的錯誤檢查和判斷邏輯簡化掉,類似於下方的程式碼:

let url = URL.init(string: "http://example.com")
let session = URLSession.shared
session.dataTask(with: url!).resume()複製程式碼

Response Handling

Alamofire預設有五種response handler:

  • Response Handler - Unserialized Response(沒有序列化response)
  • Response Data Handler - Serialized into Data(將資料序列化為Data)
  • Response String Handler - Serialized into String(將資料序列化為String)
  • Response JSON Handler - Serialized into Any(將資料序列化為Any)
  • Response PropertyList (plist) Handler - Serialized into Any(同上)

Tip:上述五種response handler都沒對返回資料的HTTPURLResponse做驗證,Alamofire是通過Response Validation做驗證的。

Response Handler
Alamofire.request("https://httpbin.org/get").response { response in
}複製程式碼

ResponseSerialization.swift中的方法實現:

 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)
    }複製程式碼

DefaultDataResponse為結構體,包含request、response、data等資料。

Alamofire不建議使用這種response serializers。

Response Data Handler
Alamofire.request("https://httpbin.org/get").responseData { response in
}複製程式碼

如果請求資料和解析資料的過程中沒有發生錯誤,它將返回一個Data型別的資料,Response的Result將會是.success。在該方法的實現原始碼中呼叫了responseSerializer: DataRequest.dataResponseSerializer()序列化資料的方法

Response String Handler
Alamofire.request("https://httpbin.org/get").responseString { response in
}複製程式碼

該Handler通過responseStringSerializer將返回的Data資料轉為String型別,如果沒有錯誤發生,並且伺服器資料成功轉為String,response的Result將為.success並且值為String。

Response JSON Handle
Alamofire.request("https://httpbin.org/get").responseJSON { response in
}複製程式碼

該handler通過呼叫jsonResponseSerializer方法來轉換資料型別,若沒發生錯誤request同上

Chained Response Handlers

Tip:使用鏈式response包含幾個handler,就會請求幾次資料資料

Response Handler Queue

Response handlers預設在主執行緒佇列中執行,但是我們可以提供一個自定義執行緒佇列

Response Validation

預設情況下,Alamofire認為任何已完成的請求都是成功的,無論相應內容是什麼。你可以在handler之前呼叫validate來判斷status code

HTTP Methods

Alamofire支援HTTP的GET、POST、DELETE等方法,並將這些方法宣告為HTTPMethod的enum

Parameter Encoding

Alamofire提供三種引數編碼方式URL,JSON和PropertyList

Session Manager

SessionManager.swift是Alamofire中比較重要的一個檔案,它是URLSession的管理類,檔案中是對request方法的具體實現。正是有該類才使得Alamofire發起一個請求非常簡單。
通過原始碼我們可以看見如果不設定URLSessionConfiguration,預設為URLSessionConfiguration.default
你也可以將URLSessionConfiguration修改為Background Configuration和Ephemeral Configuration

Request

Requests可以被暫停(suspended)復位(resumed)和取消(cancelled)

Routing Requests

隨著APP的不斷更新和迭代,構建網路層的通用模式很重要。如何路由你的request也是非常重要的一個部分。URLConvertibleURLRequestConvertible協議可以幫助你構建路由的通用部分

URLConvertible

String, URL, 和URLComponents是預設遵守URLConvertible協議的,所以你可以在request中寫String, URL, 和URLComponents,它會自動檢測request中的引數,根據引數型別找到相應的方法轉為URL。你也可以根據文件中的例子來構造自己的路由

URLRequestConvertible

URLRequest預設遵守該協議,該協議用來構造URLRequest

程式碼中的關鍵字解釋

  • @discardableResult:此關鍵字表示修飾的方法不必接受返回值Swift 3.0 中方法的返回值必須有接收否則會報警告,當然其實主要目的是為了避免開發人員忘記接收返回值的情況,但是有些情況下確實不需要使用返回值可以使用"_"接收來忽略返回值。當然你也可以增加@discardableResult宣告,告訴編譯器此方法可以不用接收返回值
  • open:open 一個元素在其他module中還是可以被override
  • static:在方法的func關鍵字之前加上關鍵字static或者class都可以用於指定類方法.不同的是用class關鍵字指定的類方法可以被子類重寫
  • @escaping

總結

至此,Alamofire的原始碼已經瞭解完,我在這裡只是簡單的瞭解一下Alamofire是如何實現最基本的GET和POST請求如何實現。可以看到,它通過幾層包裝實現了引數的校驗、request支援String、URL和URLComponents三種方式。由於水平有限,對於它下載上傳等功能的原始碼並沒有十分理解,所以在這並沒有寫,大家有能力的可以自行檢視原始碼。在Alamofire中比較重要的幾個檔案Alamofire.swift/Response.swift/SessionManager.swift/Request.swift。

github地址

參考

相關文章