Swift編寫自己的API客戶端

BigNerdCoding發表於2016-02-24

Swift編寫自己的API客戶端

作者:Nick O`Neill,文章中程式碼檔案下載
翻譯:BNCoding, 如有錯誤歡迎指出。原文連結

就像很多iOS開發者一樣,我也使用AFNetworking類庫來處理所有的網路操作(在Swift中與之對應的是Alamofire)。並且因為這些類庫的存在開發者會覺得這意味著自己做類似的工作是困難或者昂貴的。在以前確實是這樣的!在iOS6和之前版本中NSURLConnetion執行或者封裝成一個便於使用、節約時間的類庫是很痛苦的過程。

事實上從iOS7版本的NSURLSession後,網路操作變的比以前簡單多了,而且編寫自己的API客戶端能夠簡化哪些依賴性。如果這些不必要的依賴不足以說服你,那麼試想一下加入第三方的類庫程式碼可能會帶來一些不必要的bug,或者是當你只需要使用一個大類庫中的很少一部分功能的時候考慮到你應用的二進位制大小。

一個簡單的NSURLSession例子

雖然NSURLSession很簡單,我們還是來看看下面這個簡單的示例:

let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

let request = NSURLRequest(URL: NSURL(string: "http://yourapi.com/endpoint")!)

let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
    if let data = data {
        let response = NSString(data: data, encoding: NSUTF8StringEncoding)
        print(response)
    }
}
task.resume()

上面程式碼中三個最主要的組成中的第一個就是NSURLSession。在本文中它並不需要進行專門的配置。它會處理我們傳遞分配的所有的資料或者下載任務,並呼叫block塊程式碼返回結果。

希望你早就熟悉NSURLRequest了,NSURLRequest包含了網路請求的細節包括:URL、方法、其它引數等等。最簡單的配置就是指定一個URL連結,因為預設方法為GET

接下來我對最後一個也就是NSURLSessionDataTask進行一下分析。它包含了一個block程式碼(也就是Swift中的閉包),該段程式碼會在請求得到響應的時候觸發執行。我們會獲得三個可選的型別變數:包含響應得到的原始資料的NSData,從響應中得到的帶有後設資料的NSURLResponse物件,以及可能的錯誤NSError

你自己的API客戶端

現在我們已經對NSURLSession有了基本的瞭解,接下來我們將這些基本的操作封裝成一個簡單的API客戶端。

這裡最重要的核心部分是:封裝一個包含NSURLRequest和方法名稱、並返回成功與否和成功後得到的JSON資料的簡單資料作業。如果你的API返回的是XML結構的資料,只需要對響應的部分進行修改,其餘部分是相同的。

注意我們檢查程式碼成功響應部分的方式!

private func dataTask(request: NSMutableURLRequest, method: String, completion: (success: Bool, object: AnyObject?) -> ()) {
    request.HTTPMethod = method

    let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

    session.dataTaskWithRequest(request) { (data, response, error) -> Void in
        if let data = data {
            let json = try? NSJSONSerialization.JSONObjectWithData(data, options: [])
            if let response = response as? NSHTTPURLResponse where 200...299 ~= response.statusCode {
                completion(success: true, object: json)
            } else {
                completion(success: false, object: json)
            }
        }
    }.resume()
}

接下來,我們將一些常用的請求封裝到一個指定HTTP方法和completion塊(閉包)的小方法裡面。當我們將這些融合到一起的時候將變得更有意義。

private func post(request: NSMutableURLRequest, completion: (success: Bool, object: AnyObject?) -> ()) {
    dataTask(request, method: "POST", completion: completion)
}

private func put(request: NSMutableURLRequest, completion: (success: Bool, object: AnyObject?) -> ()) {
    dataTask(request, method: "PUT", completion: completion)
}

private func get(request: NSMutableURLRequest, completion: (success: Bool, object: AnyObject?) -> ()) {
    dataTask(request, method: "GET", completion: completion)
}

需要簡化的最後一個功能是,建立一個帶有我們需要傳送資料的NSURLRequest。這樣我們還可以將引數編碼為表單資料並且當擁有許可權時還可以進行授權。在不同API介面變化時,該方法的變化是最大的,但是該方法的職能依舊保持不變。

private func clientURLRequest(path: String, params: Dictionary<String, AnyObject>? = nil) -> NSMutableURLRequest {
    let request = NSMutableURLRequest(URL: NSURL(string: "http://api.website.com/"+path)!)
    if let params = params {
        var paramString = ""
        for (key, value) in params {
            let escapedKey = key.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
            let escapedValue = value.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
            paramString += "(escapedKey)=(escapedValue)&"
        }

        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
    }

    if let token = token {
        request.addValue("Bearer "+token, forHTTPHeaderField: "Authorization")
    }

    return request
}

最後,我們可以通過我們的API客戶端開始網路請求了。下面是一個簡單的的登入請求,使用了POST方法並將電子郵件和密碼作為該登入請求的引數。在這個方法裡你可以訪問廣義上的是否成功的標記量success,並且還有可能包含相關資料的Dictionary物件。

func login(email: String, password: String, completion: (success: Bool, message: String?) -> ()) {
    let loginObject = ["email": email, "password": password]

    post(clientURLRequest("auth/local", params: loginObject)) { (success, object) -> () in
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            if success {
                completion(success: true, message: nil)
            } else {
                var message = "there was an error"
                if let object = object, let passedMessage = object["message"] as? String {
                    message = passedMessage
                }
                completion(success: true, message: message)
            }
        })
    }
}

在使用者登入成功或者將返回資料寫入一個struct但是呼叫方法還沒有返回時,你可能希望回收、儲存token令牌。對於這種具體的請求行為就需要小心對待了。

我們可以針對每一個請求的類在API裡面編寫這樣的小功能,並且進行自定義為呼叫程式碼提供一個一致且簡單的體驗(可能是在檢視控制器的某處)。我們不必擔心那些編碼值或者是NSURL物件的生成,因為我們這個簡單的小封裝已經很好的解決這些問題了。

下面是我喜歡這篇文章的一些理由:

Easy to reason about
我們抽離了一部分我們不感興趣或者是重複部分,但是對於我們需要理解的Http概念的程式碼依舊進行了講解:提交一個url、一個方法名稱以及一個標記成功與否的變數和獲得的資料。

Flexible for different APIs
建立一個URL請求是一個經常需要做的事情,而這件事又由於不同人寫的服務端程式碼的不同而需要進行調整修改。通常情況下作為移動開發者我並不能控制伺服器具體的實現,所以可以進行自定義是每個專案的關鍵。

Short and sweet
這個基礎的API客戶端程式碼低於50行。如果我開始寫一個很重量級的庫去替代依賴性時,那將是件很讓人崩潰事情,尤其當它還是一個工具時我永遠都不會對它再進行任何修改。而這個API客戶端是簡單短小的,你可以頻繁的對它進行修改和新增新方法。

相關文章