iOS REST APIs: URLSessionDataTask & Alamofire

Photo by Vladislav Muslakov on Unsplash
Photo by Vladislav Muslakov on Unsplash
開發手機程式時,我們常常需要串接後端的 RESTful APIs。目前常用的是 URLSession 和 Alamofire。URLSession 是 iOS SDK 內建的;Alamofire 則是非常熱門的第三方套件。本章會講解如何透過它們串接 RESTful API 並取得 JSON 資料。

開發手機程式時,我們常常需要串接後端的 REST APIs。目前常用的是 URLSessionDataTask 和 Alamofire。URLSession 是 iOS SDK 內建的;Alamofire 則是非常熱門的第三方套件。本章會講解如何透過它們串接 REST API 並取得 JSON 資料。

本章完整的程式碼可以在 下載。

URLSessionDataTask

URLSession 是 iOS SDK 中內建的類別,而它的好處就是不用再安裝第三方套件。

GET Request

使用 URLSession 時,我們要先準備好一個 URLRequest。把 URLRequest 中的 .httpMethod 設為 GET,而它是不區分大小寫的。然後,將要傳送的資料,直接設定在 ? 後面的 Query String。

guard let url = URL(string: "https://postman-echo.com/get?foo1=bar1&foo2=bar2") else {
    print("Error: can not create URL")
    return
}

var request = URLRequest(url: url)
request.httpMethod = "get"

接下來,呼叫 URLSession.dataTask(with:) 來建立 URLSessionDataTask,並傳入 URLRequest 和 Response Handler 為參數。最後,記得要呼叫 URLSessionDataTask.resume() 來啟動這個 Task。

當它呼叫 Response Handler 時,它會給三個參數。如果第三個參數 error 不是 nil 的話,那就表示有錯誤發生。

第一個參數 data 是 RESTful API 回傳的 HTTP Body,它是 Data 型態的 JSON 字串。利用 JSONSerialization.jsonObject(with:)data 轉換成 Dictionary 型態。

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let error = error {
        print(error)
        return
    }

    guard let data = data else {
        print("Did not receive data")
        return
    }

    do {
        let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
        let args = json["args"] as! [String: String]
        print(args["foo1"]!)
        print(args["foo2"]!)
    } catch {
        print("Error: can not convert data to JSON")
        return
    }
}
task.resume()

POST Request

如同 GET Request,先準備好 URLRequest,並且把 .httpMethod 設為 POST。但是要傳送的資料,則要設定在 .httpBody

因為我們要傳送的是 JSON 字串,所以在 URLRequest 的 Header 裡,要將 Content-Type 設為 application/json,後端才知道 HTTP Body 裡的是 JSON 字串 。 我們先把資料準備好成 Dictionary 型態,再用 JSONSerialization.data(withJSONObject:) 轉成 Data 型態的 JSON 字串後,才能給 .httpBody

guard let url = URL(string: "https://postman-echo.com/post") else {
    print("Error: can not create URL")
    return
}

var request = URLRequest(url: url)
request.httpMethod = "post"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let data = [
    "foo1": "bar1",
    "foo2": "bar2",
]

do {
    request.httpBody = try JSONSerialization.data(withJSONObject: data)
} catch {
    print(error)
    return
}

之後和 GET Request 一樣,呼叫 URLSession.dataTask(with:) 來傳送資料。Handler 裡也是相同的方式來處理資料。

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let error = error {
        print(error)
        return
    }

    guard let data = data else {
        print("Did not receive data")
        return
    }

    do {
        let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
        let args = json["data"] as! [String: String]
        print(args["foo1"]!)
        print(args["foo2"]!)
    } catch {
        print("Error: can not convert data to JSON")
        return
    }
}
task.resume()

Alamofire

URLSessionDataTask 使用起來並不是很順手。例如,GET 的資料要自己手動串在 Query String。POST 的資料更麻煩,要把資料弄成 JSON 字串,再轉換成 Data 型態。此外,在 Handler 中資料的轉換處理也是都要自己來。

Alamofire 主要就是幫助我們處理這些資料格式的轉換。讓我們來看看它是怎麼運作的。

首先要先安裝 Alamofire 套件,如果你是使用 CocoaPods 的話,將這行放入你的 Podfile 中。

pod 'Alamofire', '~>5.2'

如果你不熟悉 CocoaPods 操作的話,可以參考這篇。

GET Request

我們把要傳送的資料準備好成 Dictionary 型態。呼叫 AF.request(),然後在 .responseJSON() 中傳入 Response Handler。

.responseJSON() 會先將回來的 JSON 字串轉成 Dictionary 型態,然後我們直接使用就可以了。

import Alamofire

let parameters = [
    "foo1": "bar1",
    "foo2": "bar2",
]
AF.request("https://postman-echo.com/get", method: .get, parameters: parameters)
    .responseJSON { response in
        switch response.result {
        case .success(_):
            let json = response.value as! [String: Any]
            let args = json["args"]! as! [String: String]
            print(args["foo1"]!)
            print(args["foo2"]!)

        case .failure(let error):
            print(error)
        }
    }

POST Request

發送 POST 的方式和 GET 是幾乎是一樣的。我們也要把 Content-Type 設為 application/json,以告訴後端是 JSON 字串。

另外,我們還要傳入 JSONEncoding.default。這是要告訴 Alamofire 將 parameters 轉成 JSON 字串。

let headers: HTTPHeaders = [
    "Content-Type": "application/json"
]
let parameters = [
    "foo1": "bar1",
    "foo2": "bar2",
]
AF.request("https://postman-echo.com/post",
           method: .post,
           parameters: parameters,
           encoding: JSONEncoding.default,
           headers: headers)
    .responseJSON { response in
        switch response.result {
        case .success(_):
            let json = response.value as! [String: Any]
            let args = json["data"]! as! [String: String]
            print(args["foo1"]!)
            print(args["foo2"]!)

        case .failure(let error):
            print(error)
        }
    }

Alamofire 進階使用

剛剛介紹的 Alamofire 使用方式,已經相當地簡單化了 RESTful API 的請求。但是,問題是我們還是在用 Dictionary 型態來處理資料,而不是用物件 (Object)。讓我們修改剛剛的程式。

宣告 struct PostRequest 來表示要傳送的資料,並實作 Encodable。此外,我們還要為 Encodable 加入一個 Computed Properties – var dictionary: [String: Any]。它會把物件轉換成 Dictionary

extension Encodable {
    var dictionary: [String: Any] {
        (try? JSONSerialization.jsonObject(with: JSONEncoder().encode(self))) as? [String: Any] ?? [:]
    }
}

struct PostRequest: Encodable {
    let foo1: String
    let foo2: String
}

宣告 struct PostResponsestruct PostResponseData 來表示回傳的資料,並實作 Decodable

struct PostResponse: Decodable {
    let data: PostResponseData
}

struct PostResponseData: Decodable {
    let foo1: String
    let foo2: String
}

然後改用 .responseDecoablde(of:) 來處理回傳的資料,並告訴 Alamofire 將回傳的資料自動轉成 PostResponse 物件。最後的程式如下:

let headers: HTTPHeaders = [
    "Content-Type": "application/json"
]
let parameters = PostRequest(foo1: "bar1", foo2: "bar2")
AF.request("https://postman-echo.com/post",
           method: .post,
           parameters: parameters.dictionary,
           encoding: JSONEncoding.default,
           headers: headers)
    .responseDecodable(of: PostResponse.self) { response in
        switch response.result {
        case .success(let value):
            print(value.data.foo1)
            print(value.data.foo2)

        case .failure(let error):
            print(error)
        }
    }

修改後的程式是不是更加地物件導向呢!

結語

REST API 是前後端溝通最流行的方式。在 iOS 開發時,我們必須要處理大量的 REST API。比起 URLSessionDataTask,Alamofire 使用起來更為便利。它不但幫我們轉換回傳的資料,還統一了 GETPOST 的請求方式。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue 教學

GCD 提供有效率的並行處理,讓我們不需要直接管理多執行緒。它的 Dispatch Queues 可以循序地(serially)或是並行地(concurrently)執行任務。我們只需要將要並行的程式當作任務提交到 dispatch queues 就可以了。
Read More
Photo by Florinel Gorgan on Unsplash
Read More

如何製作一個 XCFramework

XCFramework 讓你可以將 iPhone、iPhone 模擬器等多的不同平台的二進位碼打包到一個可發佈的 .xcframework 檔。你只需要為你的 Framework 產生出一個 .xcframework 檔,就可以支援多種平台。
Read More
Photo by Fabian Gieske on Unsplash
Read More

SwiftUI @State & @Binding 教學

SwiftUI 推出了兩個 Property Wrapper – @State and @Binding。利用它們可以達到變數的 Two-way Binding 功能。也就是當變數的值改變時,它會重新被顯示。本章藉由製作一個 Custom View 來展示如何使用 @State 和 @Binding。
Read More
Photo by Svitlana on Unsplash
Read More

iOS:禁止螢幕截圖

基於一些理由,我們可能會想要禁止使用者對我們的 app 做螢幕截圖。然而,iOS 並沒有提供這樣的功能。所幸,我們可以利用 UITextField 來達到此效果。
Read More