使用 URLSession 串接 REST API,練習 http get,post,delete,put,以 Reqres API 為例

開發 iOS App 時,我們時常需要跟網路上的後台(server) 溝通。如下圖所示,我們的 App 是 client,它會發送 request 給 server,然後 server 再回傳 response,比方將 JSON 格式的電影資料回傳給 App。

HTTP method (request type)

當 App 發送 request 給網路上的 server 時,目前最常見的方法是透過 http 或 https,而 http(https) 傳送的 request 有個重要的 method 欄位,可以說明 request 的目的為何。

比較常用的 http(https) method 有以下幾種:

  • GET: 取得資料。
  • POST: 建立資料。
  • DELETE: 刪除資料。
  • PUT / PATCH: 修改更新資料。

使用 PostMan 測試 API 時,可從下圖的選單設定 HTTP method。

詳細的說明可參考以下連結。

使用 HTTP method 表示 request 目的

串接 API 有很多寫法,因為後台工程師可以自訂資料的格式和處理方式,比方他可以規定讀取,新增和刪除資料的 request 都是 post,透過上傳的 JSON 資料區分讀取,新增和刪除。

不過目前比較常見的主流是採用 REST API(RESTful API),它可以讓我們串接的 API 有個統一的標準,使用 HTTP method 的 GET,POST,DELETE & PUT 區分讀取,新增,刪除和修改。想了解 REST API 的朋友,可進一步參考以下連結。

方便測試 API 的 reqres

為了示範如何使用 URLSession 串接 http get,post,delete,put API,接下來我們將以方便測試 API 的 reqres 為例說明。

Reqres API 提供豐富的 API,幫助我們方便測試 GET,POST,DELETE,PUT & PATCH。

Reqres 在 Swagger UI 的 API 文件。

抓取資料的 GET

reqres API 說明。

呼叫 dataTask(with:completionHandler:),傳入 URL 即可產生搭配 http method GET 的 request 抓取資料。

  • 使用 async/await。
let url = URL(string: "https://reqres.in/api/users?page=1")!
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let content = String(data: data, encoding: .utf8) {
print(content)
}
} catch {
print(error)
}
}
  • 使用 completion handler。
let url = URL(string: "https://reqres.in/api/users?page=1")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data,
let content = String(data: data, encoding: .utf8) {
print(content)
}
}.resume()

也可以呼叫 dataTask(with:completionHandler:),傳入 URLRequest,它也會搭配 http method GET。

  • 使用 async/await
let url = URL(string: "https://reqres.in/api/users?page=1")!
var request = URLRequest(url: url)
Task {
let (data, _) = try await URLSession.shared.data(for: request)
if let content = String(data: data, encoding: .utf8) {
print(content)
}
}
  • 使用 completion handler
let url = URL(string: "https://reqres.in/api/users?page=1")!
var request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data,
let content = String(data: data, encoding: .utf8) {
print(content)
}
}.resume()

建立資料的 POST

reqres API 說明。

  • 定義上傳和後台回台回傳的資料型別。
struct CreateUserBody: Encodable {
let name: String
let job: String
}

struct CreateUserResponse: Decodable {
let name: String
let job: String
let id: String
}
  • 設定 request。
let url = URL(string: "https://reqres.in/api/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
let user = CreateUserBody(name: "Peter", job: "情歌王子")
let data = try? encoder.encode(user)
request.httpBody = data

說明

request.httpMethod = "POST"

將 httpMethod 設為 POST,可以寫大寫的 POST,也可以寫小寫的 post。

request.setValue("application/json", forHTTPHeaderField: "Content-Type")

將 http header 的 Content-Type 設為 application/json,因為 reqres API 上傳的資料格式為 JSON。上傳的格式不一定會是 json,比方 application/x-www-form-urlencoded & multipart/form-data 也是常見的格式。

request.httpBody = data

從 URLRequest 的 httpBody 設定上傳的資料。

  • 發送 request

1 使用 async/await。

Task {
do {
let (data, _) = try await URLSession.shared.data(for: request)
let decoder = JSONDecoder()
let createUserResponse = try decoder.decode(CreateUserResponse.self, from: data)
print(createUserResponse)
} catch {
print(error)
}
}

2 使用 completion handler。

URLSession.shared.dataTask(with: request) { data, response, error in
if let data {
do {
let decoder = JSONDecoder()
let createUserResponse = try decoder.decode(CreateUserResponse.self, from: data)
print(createUserResponse)
} catch {
print(error)
}
}
}.resume()

除了使用 dataTask,我們也可以用 uploadTask,將資料帶在參數 from 裡。

1 使用 async/await。

let encoder = JSONEncoder()
let user = CreateUserBody(name: "Peter", job: "情歌王子")
if let data = try? encoder.encode(user) {
Task {
do {
let (data, _) = try await URLSession.shared.upload(for: request, from: data)
let decoder = JSONDecoder()
let createUserResponse = try decoder.decode(CreateUserResponse.self, from: data)
print(createUserResponse)
} catch {
print(error)
}
}
}

2 使用 completion handler。

let encoder = JSONEncoder()
let user = CreateUserBody(name: "Peter", job: "情歌王子")
if let data = try? encoder.encode(user) {
URLSession.shared.uploadTask(with: request, from: data) { data, response, error in
if let data {
do {
let decoder = JSONDecoder()
let createUserResponse = try decoder.decode(CreateUserResponse.self, from: data)
print(createUserResponse)
} catch {
print(error)
}
}
}.resume()
}

ps: 我們也可以解碼 JSON 裡的時間欄位,比方剛剛例子的 createdAt,相關說明可參考以下連結的 custom 解法。

刪除資料的 DELETE

reqres API 說明。

  • 使用 async/await。
let url = URL(string: "https://reqres.in/api/users/2")!
var request = URLRequest(url: url)
request.httpMethod = "delete"
Task {
do {
let (_, response) = try await URLSession.shared.data(for: request)
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
}
} catch {
print(error)
}
}
  • 使用 completion handler。
let url = URL(string: "https://reqres.in/api/users/2")!
var request = URLRequest(url: url)
request.httpMethod = "delete"
URLSession.shared.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
}

}.resume()

說明

let url = URL(string: "https://reqres.in/api/users/2")!

依據 reqres API 的說明,刪除的資料由 url 的 user id 決定。

request.httpMethod = "delete"

將 httpMethod 設為 delete。

print(httpResponse.statusCode)

依據 reqres API 的說明,刪除成功時 http status code 為 204。

更新資料的 PUT / PATCH

reqres API 說明。

寫法跟前面的 post 類似,主要的差別在 http method 設為 put 或 patch。

request.httpMethod = "put"

串接 reqres API

串接第三方 API 時,依據 Apple 的教科書,建議的寫法有以下 5 種,有興趣的朋友可進一步參考文章裡的說明。

利用 capture list 解決 closure 可能產生的記憶體問題

呼叫 API 時,我們也可以在 closure 裡加上 capture list,比方 [weak self]

class ViewController: UIViewController {
var content: String?

func fetchData() {
let url = URL(string: "https://reqres.in/api/users?page=1")!
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self,
let data,
let content = String(data: data, encoding: .utf8) else {
return
}
self.content = content
}.resume()
}

它的好處可參考以下連結的說明。

其它方便練習的 API

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com