この記事でわかること
- SwiftでHTTP通信を利用する方法
- 外部サーバからアプリにデータをダウンロードする方法
- アプリから外部サーバにデータをアップロードする方法
動作確認済みの環境
【XCode】15.3
【Swift】5.10
【iOS】17.5.1
【macOS】Sonoma 14.4.1
はじめに
この記事では、Swiftを使って外部サーバからデータを取得したり、逆にサーバへデータを送信する方法についてご紹介します。
外部サーバとの通信には、一般的にHTTP(Hypertext Transfer Protocol)を用います。
本題に入る前に、まずはHTTP通信のざっくりとした流れをまとめておきます。
①クライアントからサーバへ、何らかの要求を送る(リクエストメッセージの送信)
②サーバは要求の内容を確認し、それに対する返答を行う(レスポンスメッセージの返送)
③クライアントは返答を受け取り、要求が満たされたことを確認する(レスポンスメッセージの受信)
HTTP通信をSwiftで実現する場合には、主に以下の型を利用します。
リクエストメッセージ: URLRequest
レスポンスメッセージ: URLResponse
, HTTPURLResponse
データの送受信を行うタスクを作成する: URLSession
,URLSessionTask
, URLSessionDataTask
続く章では、それぞれの型の役割と使い方の解説、およびサンプルプロジェクトを記載しています。ぜひご覧ください!
リクエストメッセージを作成する
まずはクライアントからサーバへの要求であるリクエストについて見ていきます。
リクエストの際に利用するSwiftの型はURLRequest
構造体です。URLRequest
の具体的な仕様を見る前に、HTTPにおけるリクエストメッセージの概念について整理しておきます。
リクエストメッセージの構成要素
HTTP通信を行う際にクライアントからサーバに送信されるリクエストメッセージは、以下の要素で構成されます。
リクエストライン
リクエストラインは、サーバへの要求を行うクライアントが「何に対して」「どうしたい」のかを表現する行で、リクエストメッセージの1行目にあたります。リクエストラインのうち、「何に対して」にあたる部分はURIで、「どうする」にあたる部分はHTTPメソッドで表現されます。
HTTPメソッドにはいくつか種類がありますが、よく用いられるのはGETメソッドとPOSTメソッドの2種類です。GETメソッドはサーバからの情報の取り出しを、POSTメソッドはサーバへの情報の送信を要求します。
POST /login HTTP/1.1
よって上記のリクエストラインは「/loginに対して、何らかの情報をアップロードしたい」という要求であることがわかります。
メッセージヘッダー
メッセージヘッダーはリクエストに関する付加的な情報を保持します。
メッセージヘッダーは、情報の種類を示すヘッダーフィールドと、そのフィールドに対応する具体的な値とのペアで成り立ちます。たとえば、クライアントが受け入れ可能なデータの種類を示すAcceptヘッダーフィールドは、以下のように表現されます。
Accept: applicaation/json
ヘッダーフィールドには他にも多くの種類があり、メッセージヘッダー全体は数行から10数行程度の設定値の羅列になることが多いようです。
メッセージボディ
メッセージヘッダーの後に空白行を1行入れたのち、メッセージボディが続きます。メッセージボディにはサーバに送るデータが記載されます。
POSTメソッドの場合にはサーバに送信する情報がメッセージボディに記載されますが、データ送信の必要がないGETメソッドの場合、メッセージボディは空白となります。
URLRequestの利用方法
リクエストメッセージの概要が掴めたところで、URLRequest
の具体的な使い方を見ていきます。
以下は、URLRequest
を用いてリクエストメッセージを作成する例です。
//リクエストメッセージの作成
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
2行目:
アクセス先のURLを作成します。
3行目:URLRequest
のイニシャライザにURLを渡し、インスタンス化を行います。
4行目:
HTTPメソッドをGETに指定します。
5行目:
受け入れ可能なデータ形式の設定を行ないます。
このようにコーディングを行うことで、下記のようなHTTPリクエストメッセージを作成できます。
GET /posts/1 HTTP/1.1 //リクエストライン
Host: jsonplaceholder.typicode.com //メッセージヘッダー
Accept: application/json
※GETメソッドのためメッセージボディはない
セッションを準備し、タスクを実行する
リクエストを作成しただけでは、通信は行われません。リクエストメッセージを実際にサーバへ送り、サーバからのレスポンスメッセージを受け取るステップが必要となります。
この一連の作業をSwiftでは「タスク」と呼び、タスクを作成するのがURLSession
クラスの主な役割です。
さっそく使い方を見てみましょう。
URLSessionを用いたセッションの作成方法
まずはURLSession
のインスタンスを作成します。
let session = URLSession.shared
URLSession
はシングルトンで設計されており、基本的にはイニシャライザ経由ではなくshared
プロパティから共有インスタンスを取得します。
タスクの作成は、URLSession
のdataTask(with:completionHandler:)
メソッドを利用することで行います。このメソッドの戻り値としてURLSessionTask
クラスのサブクラスであるURLSessionDataTask
クラスのインスタンスが返されます。
//サーバへリクエストメッセージを送信
let task = session.dataTask(with: urlRequest) { data, response, error in
//データ通信の完了時に実行される処理をここに記述する
}
dataTask(with:completionHandler:)
メソッドの第一引数with
にはURLRequest
を渡し、第二引数にはコンプリーションハンドラを渡します。コンプリーションハンドラは、リクエスト要求が完了した時に実行されます。サーバから受け取ったデータを利用して処理を行う場合には、このクロージャ内に記述します。
作成したタスクはデフォルトでは中断状態になっているため、resume()
メソッドを呼び出して実行します。
task.resume()
これでタスクが実行されます。
レスポンスを受け取り、処理する
前章までで、リクエストメッセージの作成とタスクの実行を行いました。
HTTPでは、送信したリクエストの結果はレスポンスメッセージとしてサーバから返送されます。レスポンスの内容は、タスクの作成時にdataTask(with:completionHandler:)
メソッドに渡したコンプリーションハンドラの各引数に渡されるため、クロージャ内でこれらにアクセスすることで確認することができます。
let task = session.dataTask(with: urlRequest) { data, response, error in
//ここでdata, response, errorにアクセスし、レスポンスメッセージの詳細を取得する
}
具体的なコードの説明を行う前に、まずはHTTPにおけるレスポンスメッセージの構成を押さえておきましょう。
レスポンスメッセージの構成要素
レスポンスメッセージは以下の要素で構築されています。
ステータスライン
ステータスラインはレスポンスメッセージの1行目にあたり、通信の結果(正常終了したのかエラーになったのか)を示します。
通信の結果は、ステータスラインに含まれる「ステータスコード」と「レスポンスフレーズ」の2つの表現方法で示されます。ステータスコードは3桁の数字で、レスポンスフレーズは短い文章で結果を表現します。
メッセージヘッダー
リクエストメッセージのメッセージヘッダーと同様に、レスポンスに関する付加的な細かい情報を保持します。
メッセージボディ
サーバから返送されるデータがここに記載されます。
レスポンスメッセージの扱い方
レスポンスメッセージの概要が掴めたところで、その内容をSwiftで取り扱う方法を見ていきます。
前の章で、以下タスクの作成を行いました。
let task = session.dataTask(with: urlRequest) { data, response, error in
//データ通信の完了時に実行される
}
先述した通り、レスポンスメッセージの内容は、dataTask(with:completionHandler:)
メソッドの第二引数に渡したクロージャの各引数へ渡されます。
data:
返送されたデータ(メッセージボディの中身)がData?
型で格納される
response:
ステータスコードなどのメタデータがURLResponse?
型で格納される
error:
エラー情報がError?
型で格納される
クロージャ内でオプショナルバインディングなどの適切な処理を行ったのちにアクセスすることで、レスポンスメッセージに含まれる各種データにアクセスすることが可能になります(具体的な実装例は次章で紹介します)。
サンプルプログラム
ここまで学んだことをもとに、具体的なプログラムを書いてみましょう。
GETメソッドでサーバからJSONをダウンロードするサンプルプログラム
以下は、HTTPのGETメソッドを利用して、外部サーバからJSONをダウンロードするサンプルプログラムです。
なお、このプログラムではJSONPlaceholderからJSONのデモデータをダウンロードします。JSONPlaceholderの基本的な利用方法については、以下の記事にまとめています。
URLRequest
を用いてリクエストメッセージを作成します。
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
1行目:
JSONPlaceholderからJSONのデモデータをダウンロードするためのURLを設定しています。
2行目: URLRequest
をインスタンス化します。イニシャライザには1行目で作成したURLを渡します。
3行目:
HTTPメソッドを指定しています。今回はサーバ上の情報を取得するため、GETメソッドを指定します。
4行目:
クライアント側が受け入れ可能なデータをMIME形式で指定しています。今回はJSONをダウンロードするので、 Acceptヘッダーフィールドにapplication/jsonを指定します。
次に実際の通信を実行・制御するためのセッションを作成します。
STEP1で記述したコードに、以下を追記します。
let session = URLSession.shared
URLSession
クラスは、shared
プロパティにアクセスしてインスタンス化します。必要以上のセッション確立を防ぐため、共有インスタンスを利用します。
URLSessionクラスのdataTask(with:completionHandler:)
メソッドを用いて、タスクを作成します。
STEP2で記述したコードに、以下を追記します。
let task = session.dataTask(with: url) {data, response, error in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300
else{
print("Error")
return
}
print(String(data: data, encoding: .utf8)!)
}
2行目から10行目までのguard文では、以下の4つの条件が全て満たされていることを確認しています。
①data
がnilでないこと
②エラーが発生していないこと(error
がnilであること)
③URLResponse
型からHTTPURLResponse
型へのダウンキャストが成功すること
④ステータスコードが200番台であること
これらを確認した後、print()
メソッドを用いてデータをデバッグエリアに出力します。
STEP3で作成したタスクは、デフォルトでは中断状態となっているため、手動で実行を行います。
STEP3で追記したコードに、以下を追記します。
task.resume()
これでタスクが実行されます。
通信が完了した後、dataTask(with:completionHandler:)
メソッドの第二引数に渡したコンプリーションハンドラが作動します。エラーが検出されず、デバッグエリアに以下の内容が出力されれば成功です。
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
<参考>コード全文
import Foundation
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let session = URLSession.shared
let task = session.dataTask(with: url) {data, response, error in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300
else{
return
}
print(String(data: data, encoding: .utf8)!)
}
task.resume()
POSTメソッドでサーバにJSONをアップロードするサンプルプログラム
以下は、HTTPのPOSTメソッドを利用して、外部サーバにJSONをアップロードするサンプルコードです。
データのアップロード先となるサーバを用意するのは面倒なので、reqres.inを利用します。
まずは、サーバに送信するJSONのデータを作成します。
//投稿データの定義
struct Post: Codable{
let userID: Int
let id: Int
let title: String
let body: String
enum CodingKeys: String, CodingKey{
case userID = "userId"
case id, title, body
}
}
//Postインスタンスの作成
let newPost = Post(userID: 1, id: 1, title: "New Post", body: "This is a new post.")
//データをJSON形式にエンコード
var data: Data? = nil
do{
data = try JSONEncoder().encode(newPost)
}catch{
print("Error: \(error.localizedDescription)")
}
上記のコードでは投稿データを表すPost構造体のインスタンスを作成し、JSONEncoder
を用いてJSON形式へのエンコードを行っています。
カスタム構造体のインスタンスをJSONにエンコードする詳しい方法については、以下の記事にまとめています。
URLRequest
を用いてリクエストメッセージを作成します。
STEP1で記述したコードに、以下を追記します。
let url = URL(string: "https://reqres.in/api/post")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = data
1行目:
アクセス先のURLを指定します。ここではreqres.inにJSONデータをアップロードするためのURLを作成しています。
2行目:URLRequest
をインスタンス化します。イニシャライザには1行目で作成したURLを渡します。
3行目:
HTTPメソッドを指定しています。今回はサーバにデータをアップロードするため、POSTメソッドを指定します。
4行目:
送信を行うデータの種類をMIME形式で指定しています。今回はJSONのデータの送受信を行うので、Content-TypeヘッダーフィールドとAcceptヘッダーフィールドの両方にapplication/jsonを指定します。
5行目:
メッセージボディに、サーバへ送信するデータを設定します。
次に実際の通信を実行・制御するためのセッションを作成します。
STEP2で記述したコードに、以下を追記します。
let session = URLSession.shared
URLSession
クラスのsharedプ
ロパティにアクセスし、インスタンス化します。必要以上のセッション確立を防ぐため、共有インスタンスを利用します。
URLSession
クラスのdataTask(with:completionHandler:)
メソッドを用いて、タスクを作成します。
STEP3で記述したコードに、以下を追記します。
let task = session.dataTask(with: urlRequest) { data, response, error in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300
else{
print("Error")
return
}
//次のSTEPでここに処理を追加する
}
2~10行目のguard文で行うチェックは、GETリクエストの時と同じ以下の4つです。
①data
がnilでないこと
②エラーが発生していないこと(error
がnilであること)
③URLResponse
型からHTTPURLResponse
型へのダウンキャストが成功すること
④ステータスコードが200番台であること
リクエストメッセージの送信先であるreqres.inは、POSTしたデータをそのまま返すAPIです。つまり通信の完了後、レスポンスメッセージに含まれるデータがリクエストメッセージに格納したデータと一致していれば、正しくアップロードが行われたことになります。
この確認を行うため、STEP4の「//次のSTEPでここに処理を追加する」の箇所に、以下を追記します。
let returnedPost: Post
do{
returnedPost = try JSONDecoder().decode(Post.self, from: data)
}catch{
print("Error: \(error.localizedDescription)")
return
}
print("User ID: \(returnedPost.userID)")
print("ID: \(returnedPost.id)")
print("Title: \(returnedPost.title)")
print("Body: \(returnedPost.body)")
1~7行目:
サーバから返送されたデータを、Post
型にデコードする処理を行っています。
9~12行目:print()
メソッドを用いてPost
インスタンスの各プロパティをデバッグエリアに出力しています。
STEP4・5で作成したタスクは、デフォルトでは中断状態となっているため、手動で実行を行います。
STEP4のコードの下に以下を追記します。
task.resume()
これでタスクが実行されます。
通信が完了した後、dataTask(with:completionHandler:)
メソッドの第二引数に渡したコンプリーションハンドラが作動します。エラーが検出されず、デバッグエリアに以下の内容が出力されれば成功です。
User ID: 1
ID: 1
Title: New Post
Body: This is a new post.
<参考>コード全文
import Foundation
struct Post: Codable{
let userID: Int
let id: Int
let title: String
let body: String
enum CodingKeys: String, CodingKey{
case userID = "userId"
case id, title, body
}
}
let newPost = Post(userID: 1, id: 1, title: "New Post", body: "This is a new post.")
var data: Data? = nil
do{
data = try JSONEncoder().encode(newPost)
}catch{
print("Error: \(error.localizedDescription)")
}
let url = URL(string: "https://reqres.in/api/post")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = data
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { data, response, error in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300
else{
print("Error")
return
}
let returnedPost: Post
do{
returnedPost = try JSONDecoder().decode(Post.self, from: data)
}catch{
print("Error: \(error.localizedDescription)")
return
}
print("User ID: \(returnedPost.userID)")
print("ID: \(returnedPost.id)")
print("Title: \(returnedPost.title)")
print("Body: \(returnedPost.body)")
}
task.resume()
おわりに
今回は、Swiftを使って外部サーバからデータを取得したり、逆にサーバへデータを送信する方法ついてご紹介しました。
本文中で紹介しきれなかった細かい仕様に関しては、以下の参考文献をご覧ください!
以上、参考になれば嬉しいです。
ここまでお読みくださり、ありがとうございました????
※本記事は、著者が学習した内容をまとめたものとなります。内容の精査につきましては、執筆時の技術力で可能な限りの注意を払っていますが、万が一誤りがございましたらフォームからご一報いただけると幸いです????????
参考文献
- 戸根 勤. ネットワークはなぜつながるのか 知っておきたいTCP/IP、LAN、光ファイバの基礎知識. 日経BP社, 2019, p21~p47
→URLとHTTPの仕組みを復習するために用いた。 - 石川洋資, 西山勇世. [増補改訂第3版]Swift実践入門 直感的な文法と安全性を兼ね備えた言語. 技術評論社, 2021. p366~p370
→Foundationを利用したWebサービスとの連携に用いる、URLRequest型, HTTPURLResponse型, URLSessionクラス, URLSessionDataTaskクラスの基礎を学習するために用いた。 - Paul Hudson. Hacking with iOS(SwiftUI Edition). Hacking with Swift. 2024. p330~370
→サンプルプロジェクトを通じてHTTP通信をアプリの機能として実装する方法を学んだ。 - Swiftful Thinking. “Download JSON from API in Swift w/ URLSession and escaping closures | Continued Learning #22”. Youtube. 2021-04, (参照 2024-06-17)
→サンプルプロジェクトを通じてHTTP通信をアプリの機能として実装する方法を学んだ。また、コンプリーションハンドラの利用方法を学んだ。 - Apple Inc. “URL Loading System”. AppleDeveloper, (参照 2024-06-18)
→ここから各種ドキュメントに飛ぶことができる - Apple Inc. “Fetching website data into memory”. AppleDeveloper, (参照 2024-06-18)
→URLSessionDataTaskクラスを用いた、小さなデータのやり取りを行うタスクの実装方法について書かれている。コンプリーションハンドラを用いたシンプルな方法と、デリゲートを用いたカスタマイズ方法が記載されているが、本記事では前者のみ取り扱っている。 - Apple Inc. “Uploading data to a website”. AppleDeveloper, (参照 2024-06-18)
→アプリからサーバへデータをアップロードする際の手順が記載されている。 - Apple Inc. “URLRequest”. AppleDeveloper, (参照 2024-06-18)
- Apple Inc. “URLSession”. AppleDeveloper, (参照 2024-06-18)
- Apple Inc. “URLResponse”. AppleDeveloper, (参照 2024-06-18)
- Apple Inc. “HTTPURLResponse”. AppleDeveloper, (参照 2024-06-18)
- Apple Inc. “URLSessionTask”. AppleDeveloper, (参照 2024-06-18)
- Apple Inc. “URLSessionDataTask”. AppleDeveloper, (参照 2024-06-18)
コメント