【Swift】アプリとサーバ間でHTTP通信を行い、データのやり取りをする方法

この記事でわかること

  • 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プロパティから共有インスタンスを取得します。

タスクの作成は、URLSessiondataTask(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桁の数字で、レスポンスフレーズは短い文章で結果を表現します。

ステータスコードは最初の1桁で通信の結果を大まかに表現する。
https://triple-underscore.github.io/RFC7231-ja.html#section-6より引用

メッセージヘッダー

リクエストメッセージのメッセージヘッダーと同様に、レスポンスに関する付加的な細かい情報を保持します。

メッセージボディ

サーバから返送されるデータがここに記載されます。

レスポンスメッセージの扱い方

レスポンスメッセージの概要が掴めたところで、その内容を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の基本的な利用方法については、以下の記事にまとめています。

STEP
リクエストメッセージを作成する

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を指定します。

STEP
セッションを作成し、通信の準備をする

次に実際の通信を実行・制御するためのセッションを作成します。
STEP1で記述したコードに、以下を追記します。

let session = URLSession.shared

URLSessionクラスは、sharedプロパティにアクセスしてインスタンス化します。必要以上のセッション確立を防ぐため、共有インスタンスを利用します。

STEP
タスクを作成する

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()メソッドを用いてデータをデバッグエリアに出力します。

STEP
タスクを実行し、結果を確認する

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"
}
GOAL
完了!
<参考>コード全文
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を利用します。

STEP
送信するJSONを作成する

まずは、サーバに送信する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にエンコードする詳しい方法については、以下の記事にまとめています。

STEP
リクエストメッセージを作成する

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行目:
メッセージボディに、サーバへ送信するデータを設定します。

STEP
セッションを作成し、通信の準備をする

次に実際の通信を実行・制御するためのセッションを作成します。
STEP2で記述したコードに、以下を追記します。

let session = URLSession.shared

URLSessionクラスのsharedプロパティにアクセスし、インスタンス化します。必要以上のセッション確立を防ぐため、共有インスタンスを利用します。

STEP
タスクを作成する

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番台であること

STEP
処理を追加する

リクエストメッセージの送信先である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インスタンスの各プロパティをデバッグエリアに出力しています。

STEP
タスクを実行し、結果を確認する

STEP4・5で作成したタスクは、デフォルトでは中断状態となっているため、手動で実行を行います。
STEP4のコードの下に以下を追記します。

task.resume()

これでタスクが実行されます。

通信が完了した後、dataTask(with:completionHandler:)メソッドの第二引数に渡したコンプリーションハンドラが作動します。エラーが検出されず、デバッグエリアに以下の内容が出力されれば成功です。

User ID: 1
ID: 1
Title: New Post
Body: This is a new post.
GOAL
完了!
<参考>コード全文
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を使って外部サーバからデータを取得したり、逆にサーバへデータを送信する方法ついてご紹介しました。
本文中で紹介しきれなかった細かい仕様に関しては、以下の参考文献をご覧ください!

以上、参考になれば嬉しいです。
ここまでお読みくださり、ありがとうございました????

※本記事は、著者が学習した内容をまとめたものとなります。内容の精査につきましては、執筆時の技術力で可能な限りの注意を払っていますが、万が一誤りがございましたらフォームからご一報いただけると幸いです????????

参考文献

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次