Swift - URLSession で HTTP リクエスト (POST) する方法 - Basic 認証・JSON

ここでは、Swift で URLSession を使って、Basic 認証 をともなう HTTP リクエスト (POST) する方法をご説明します。

リクエストとレスポンスのボディは JSON でやりとりします。

Basic 認証がいらない場合は、後で説明する Basic 認証を設定している行を削除してください。

テスト用 REST API の説明

この記事のために、次のような Basic 認証(ベーシック認証)を使用した、item の情報を取得する簡単な REST API を作りました。

API の URL と Basic 認証 のユーザー名とパスワードは次の通りです。

let apiURL = "https://softmoco.com/getItemInfo.php"
let apiUsername = "APIUser"
let apiPassword = "APIPassword123"

Swift - URLSession で HTTP リクエスト 1


リクエストのボディには次のような JSON で itemCode をポストします。

{
	"itemCode": "検索したい Item Code"
}

"検索したい Item Code" が "ABC" の時のみ、レスポンスのボディには次のような Item の情報が JSON で返ります。

{
    "success": true,
    "message": "ItemCode:ABC found.",
    "item": {
        "itemCode": "ABC",
        "itemName": "ABC Item Name",
        "unitPrice": 150
    }
}

Swift - URLSession で HTTP リクエスト 2


"ABC" 以外の itemCode だと、次のようなレスポンスのボディが返ります。

{
    "success": false,
    "message": "ItemCode:XXX not found."
}

Swift - URLSession で HTTP リクエスト 3


認証情報がなかったり、Basic 認証のユーザー名やパスワードが間違っている時は Status: 401 Unauthorized を返します。

Swift - URLSession で HTTP リクエスト 4


リクエストボディーが invalid な時は Status: 400 Bad Request を返します。

Swift - URLSession で HTTP リクエスト 5


この REST API を使って、Swift から http の POST リクエストでデータを取得します。


テスト用のアプリの説明

今回使うテスト用のアプリは、次のような Text Field、Button、Text View だけのシンプルな一画面だけのアプリです。

Swift - URLSession で HTTP リクエスト 6


Text Field に「ABC」と入力して [Get Item Info] ボタンをタップすると、REST API にリクエストを送信し、レスポンスの success が true なので、以下のように受信した message と item の情報をフォーマットして表示します。

Swift - URLSession で HTTP リクエスト 7


Text Field に「ABC」以外を入力して [Get Item Info] ボタンをタップすると、REST API にリクエストを送信し、レスポンスの success が false なので、 受信した message のみを表示します。

Swift - URLSession で HTTP リクエスト 8

テスト用のアプリを作る

Xcode で [iOS] の [App] の新規プロジェクトを作成してください。

ストーリーボードでデザインは適当でも良いので、以下のように Text Field、Button、Text View を追加してください。

Swift - URLSession で HTTP リクエスト 9


ViewController.swift に以下のように Text Field と Text View のアウトレットと、Button の TouchUpInside のアクションを作っておいてください。

@IBOutlet weak var itemCodeTextField: UITextField!
@IBOutlet weak var itemInfoTextView: UITextView!

@IBAction func getItemInfoTapped(_ sender: Any) {
}

Swift - URLSession で HTTP リクエスト 10

Text Field などの追加の方法や、アウトレットやアクションの作り方がわからない方は「基本的な iOS アプリの作り方」をご覧ください。


Item 構造体 (struct) を作る

レスポンスで受け取った Item の情報を保持するために、Item.swift という名前のファイルを作って、以下のコードを追加しておいてください。

struct Item: Codable {
    var itemCode: String
    var itemName: String
    var unitPrice: Int
}

今回はちょっと情報を表示するだけなので、Item 構造体 (struct) にデータを保持するまでもないかもしれませんが、実際のアプリの開発では受け取ったデータをその後も利用すると思いますので、JSON 形式の文字列から Item オブジェクトを生成する方法をご紹介します。

URLSession で HTTP リクエスト (POST) する方法

それでは、Swift で URLSession を使って HTTP リクエスト (POST)する方法をご説明します。

getItemInfoTapped のアクションメソッドに次のコードを追加してください。

続けて、コードを順を追って説明していきます。

@IBAction func getItemInfoTapped(_ sender: Any) {
    
    itemInfoTextView.text = "";

    let itemCode = itemCodeTextField.text;

    if itemCode?.isEmpty ?? true {
        itemInfoTextView.text = "Please enter Item Code."
        return;
    }

    // API call
    let apiURL = "https://softmoco.com/getItemInfo.php"
    let apiUsername = "APIUser"
    let apiPassword = "APIPassword123"

    guard let url = URL(string: apiURL) else { return }

    let authString = String(format: "%@:%@", apiUsername, apiPassword)
    let authData = authString.data(using: String.Encoding.utf8)!
    let authBase64 = authData.base64EncodedString()

    let data: [String: Any] = ["itemCode": itemCode!]
    guard let httpBody = try? JSONSerialization.data(withJSONObject: data, options: []) else { return }

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("Basic \(authBase64)", forHTTPHeaderField: "Authorization")
    request.httpBody = httpBody

    URLSession.shared.dataTask(with: request) {(data, response, error) in

        if let error = error {
            print("Failed to get item info: \(error)")
            return;
        }

        if let response = response as? HTTPURLResponse {
            if !(200...299).contains(response.statusCode) {
                print("Response status code does not indicate success: \(response.statusCode)")
                return
            }
        }

        if let data = data {
            do {
                let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                let success = jsonDict!["success"] as! Bool
                var message = jsonDict!["message"] as! String

                if(success) {
                    let jsonData = try JSONSerialization.data(withJSONObject: jsonDict!["item"]!, options: .prettyPrinted)

                    if let item = try? JSONDecoder().decode(Item.self, from: jsonData as Data) {
                        message += "\n\n*** Item Info ***\nItem Name: \(item.itemName)\nUnit Price: \(item.unitPrice)"
                    }
                }

                DispatchQueue.main.async {
                   self.itemInfoTextView.text = message
                }
            } catch {
                print("Error parsing the response.")
            }
        } else {
            print("Unexpected error.")
        }

    }.resume()
}

それでは、コードを順を追ってご説明します。

itemInfoTextView.text = "";

let itemCode = itemCodeTextField.text;

if itemCode?.isEmpty ?? true {
    itemInfoTextView.text = "Please enter Item Code."
    return;
}

まず最初の部分は Text View のテキストのリセットと、Text Field に Item Code が入っていない時は "Please enter Item Code." というメッセージを Text View に表示してアクションメソッドの実行を終了しています。


let authString = String(format: "%@:%@", apiUsername, apiPassword)
let authData = authString.data(using: String.Encoding.utf8)!
let authBase64 = authData.base64EncodedString()

let data: [String: Any] = ["itemCode": itemCode!]
guard let httpBody = try? JSONSerialization.data(withJSONObject: data, options: []) else { return }

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Basic \(authBase64)", forHTTPHeaderField: "Authorization")
request.httpBody = httpBody

上のハイライトされているラインが、Basic 認証のためのコードで、ユーザー名とパスワードをつなげて、base64 にエンコードし、URLRequest のオブジェクトに Basic 認証用にセットしています。

Basic 認証がいらない REST API にリクエストする場合は、ハイライトされている行を削除してください。


let data: [String: Any] = ["itemCode": itemCode!]
guard let httpBody = try? JSONSerialization.data(withJSONObject: data, options: []) else { return }

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Basic \(authBase64)", forHTTPHeaderField: "Authorization")
request.httpBody = httpBody

上のハイライトされている箇所がリクエストのボディ部の準備です。

23 行目でまず、JSON にして送信したいデータを保持した dictionary を作り、24 行目で JSONSerialization.data() を使って dicionary から JSON データを生成し、30 行目でそれを URLRequest のオブジェクトの httpBody に代入しています。


URLSession.shared.dataTask(with: request) {(data, response, error) in

この行からが、URLSession の dataTask() で「リクエストの送信・レスポンスの受け取り・その後の処理」をタスクとして定義している箇所です。

先ほど生成した request オブジェクトを with で渡しています。

data にはサーバーから送られてきたデータが、response にはレスポンスの HTTP ヘッダーやステータスコードなどのレスポンスのメタデータが、error にはリクエストが失敗した時のみ、失敗した理由が入っています。


if let error = error {
    print("Failed to get item info: \(error)")
    return;
}

error に値が入っている時は、そのエラーを print して return しています。


if let response = response as? HTTPURLResponse {
    if !(200...299).contains(response.statusCode) {
        print("Response status code does not indicate success: \(response.statusCode)")
        return
    }
}

ここでは、response を HTTPURLResponse にキャストして、レスポンスのステータスコードをチェックしています。

200 番台(Success) ではない時はステータスコードを print して return しています。


if let data = data {
    do {
        let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
        let success = jsonDict!["success"] as! Bool
        var message = jsonDict!["message"] as! String

        if(success) {
            let jsonData = try JSONSerialization.data(withJSONObject: jsonDict!["item"]!, options: .prettyPrinted)

            if let item = try? JSONDecoder().decode(Item.self, from: jsonData as Data) {
                message += "\n\n*** Item Info ***\nItem Name: \(item.itemName)\nUnit Price: \(item.unitPrice)"
            }
        }

        DispatchQueue.main.async {
           self.itemInfoTextView.text = message
        }
    } catch {
        print("Error parsing the response.")
    }
} else {
    print("Unexpected error.")
}

そして、レスポンスで受け取ったデータを 48 行目で JSONSerialization.jsonObject() の戻り値を dictionary にキャストして JSON 形式の文字列から dictionary に変換しています。

success が true の時は item の情報も受け取るので、53 行目で JSONSerialization.data() を使って一旦 Data 型に変換し、55 行目でJSONDecoder().decode を使って Item オブジェクトに変換しています。

message 変数に Text View に表示したい文字列を生成して、60 ~ 62 行目で、メインスレッドではないので直接 Text View の text の値を更新できないので、DispatchQueue.main.async を使って更新するようにしています。


}.resume()

最後に resume() メソッドで定義したタスクをスタートしています。

これで「テスト用のアプリの説明」でご説明した動作をするアプリの完成です。


以上、Swift で URLSession を使って、Basic 認証 をともなう HTTP リクエスト (POST) する方法についてご説明しました。

© 2024 iOS 開発入門