【iOS/SwiftUI】Alamofireを使用してサーバーと通信する

目次

概要

現代のアプリやゲームはネット通信が大前提の時代。
だからこそ、サーバーと通信をして(「APIを叩く」などと言われる)さまざまな機能を実装できるようにしよう。
Swiftにもデフォルトで通信ができるライブラリが存在するが、この際新たにライブラリを追加してみよう。

Alamofireを導入する

はじめに

以前はCocoaPodsを使うのが主流だったが、最近はSwift Package Managerから導入する流れを見かける。
正直、Cocoapodsを入れるより手軽だと思うし、VisualCodeやAndroid Stduioなどでこの形式を見かけるため、この入れ方を知っておいた方がいいだろう。

Swift Packeage ManagerからAlamofireをインストールする

まずはSwift Package Managerを立ち上げよう。

プロジェクトファイルを開いて、「Package Dependencies」タブを選択し、「+」ボタンを押下する。

インストールできるライブラリの一覧が表示されるため、検索欄にAlamofireのgitのリポジトリのURLを記入する。

https://github.com/Alamofire/Alamofire.git

すると、以下の画面のようにalamofireを選択して、「Add Package」を押下する

インストールが開始されて、完了したら以下のようにインストールしたライブラリの確認画面に移る。そして、改めて「Add Package」を押下する。

完了するとPackagesの一覧に先ほどインストールしたライブラリが追加されていることを確認する。
こうすることで初めてAlamofireを自分のソースコード上でライブラリを使用することができる。
さあ、続いてソースコードの概要を載せていく。

ソースコード

アプリ実行部分

import SwiftUI

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            CallZipCodeView()
        }
    }
}

View表示部分

import SwiftUI

struct CallZipCodeView: View {
    @ObservedObject private var viewModel = CallZipCodeViewModel()
    @FocusState private var focusState: Bool
    
    var body: some View {
        VStack {
            Text("郵便番号を入力してください")
            Text("(ハイフン不要)")
            TextField("郵便番号", text: $viewModel.zipCode)
                .padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
                .textFieldStyle(.roundedBorder)
                .focused($focusState)
                .keyboardType(.numberPad)
            Button {
                viewModel.pushedSearchButton()
            } label: {
                Text("郵便番号取得")
            }
            TextEditor(text: $viewModel.jsonData)
                .disabled(true)
        }
        .onTapGesture {
            focusState = false
        }
    }
}

処理部分

import Alamofire
import Foundation

class CallZipCodeViewModel: ObservableObject {
    @Published var zipCode: String = ""
    @Published var jsonData = ""
    
    private let baseUrl = "https://zipcloud.ibsnet.co.jp/api/search"
    
    func pushedSearchButton() {
        let params: [String : String] = ["zipcode" : zipCode]
        AF.request(baseUrl, method: .get, parameters: params).response { response in
            guard let data = response.data else {
                print("NoData")
                return
            }
            do {
                let responseData = try JSONSerialization.jsonObject(with: data, options: [])
                print(responseData)
                self.jsonData = "\(responseData)"
            } catch {
                print("error")
            }
        }
    }
}

デモ動画

詳細

インストールしたライブラリを使用することを宣言

import Alamofire

処理部分のソースコードの頭に上記の文言が書かれている。これは、

このファイルではAlamofireで定義されているクラスやメソッドなどを使いますよ

ということの意思表示だ。
また、Alamfireはサーバーとの通信を行う役割なので、View側のファイルに書かないようにしよう。
なぜなら、サーバーから受け取ったデータを画面に表示することはあるが、サーバーとの通信処理自体は画面表示とは何も関係ないからだ。

正直ここはアーキテクチャというアプリの実装するうえでどのようなファイル構成にするかという部分だ。実務やある程度の規模のアプリを作るなら必須だが、個人学習、入門レベルならあまり気にする必要はない。

View表示部分の整理

ここではさらっと流す程度にする。
以下のことを実装している

  • 郵便番号入力欄の見出し(文言)を表示
  • 郵便番号の入力欄を表示
    • 角丸のテキストフィールドが表示されるようにする
    • キーボードは数字入力専用にする
  • ボタンを押下したらサーバー通信を行う
  • ボタンの下にサーバーから受け取ったデータを表示する

通信処理

さて、ここからが本題だ。
まず、サーバーとの通信を行うにはAF.requestメソッドを使用する。
今回のサンプルコードだと以下の部分だ。

AF.request(baseUrl, method: .get, parameters: params).response { response in
    guard let data = response.data else {
        print("NoData")
        return
    }
    do {
        let responseData = try JSONSerialization.jsonObject(with: data, options: [])
        print(responseData)
        self.jsonData = "\(responseData)"
    } catch {
        print("error")
    }
}

分解していこう

requestメソッドの引数

サーバーとの通信を行うにはAF.requestメソッドを使用する必要がある。
その引数は以下のように設定する。

引数名内容
url(第一引数)サーバー上のどのAPIを呼び出すか。
要は、どのサーバーのどのファイルと通信を行うか宣言する。

APIのファイルが置いてあるURLを記載する。
今回の場合、郵便番号から住所情報を取得するAPIをしよう
https://zipcloud.ibsnet.co.jp/api/search
methodPOST型かGET型か。
簡単に言うと
・GETは検索結果などデータを取得するもの
・POSTは登録などを行う時に使われる
parametersAPIを呼び出す際のパラメータ
このパラメータに応じて取得できる値が変化する。
アプリ開発においてのメソッドの引数と思ってもらえればいい
responseAPIからデータを取得した後に行われる処理。
responseの中にAPIから取得したデータが格納されている。
基本的に、responseの引数を使ってAPIからのデータ取得後の処理を行う

サーバーからのデータ取得後の処理

先ほど、responseの部分がサーバーからのデータ取得後の処理と書いた。
まずは、サーバーのデータが正常に取得できているかチェックしよう。

guard let data = response.data else {
    print("NoData")
    return
}

responseの中のdataというものがnilになっていないかをチェックする。
ここでnilになっているなら正常にデータのやり取りができていないこと。
そのまま処理を続けてもアプリが強制終了の原因となるため、ここで処理を中断する。
本来なら、print(“NoData”)と記載しているところに、エラーダイアログを表示させるための処理を書いておくのがいいだろう。(ここは処理実装のファイルのため、「ダイアログを表示するためのフラグをたてる」という処理が望ましい)

しかし、ここで変数「data」の中身を表示しようとしても、Data型のため僕たち開発者が扱える形ではない。
今回はJSONデータにして表示してみよう。(クラス型に格納するというのは後日記載するので安心して欲しい)

do {
    let responseData = try JSONSerialization.jsonObject(with: data, options: [])
    print(responseData)
    self.jsonData = "\(responseData)"
} catch {
    print("error")
}

JSONSerialization.jsonObjectメソッドを使用して、Data型の変数をJSON型にしている。
そして、取得したデータを文字列にしている。
ただ、もしも変数「data」がJSONの形式でなかった場合は正常に変換できないため、その場合も考慮してcatch文にエラーだった場合の処理も書いておこう。

そして、そのJSONの文字列をjsonDataに格納して更新することで、View側でその更新内容をViewに反映させるようにしている。(@Publishedにしているため、更新内容がViewに知らされる)

余談

今回は郵便番号から住所の情報を取得するAPIを使用した。
ただ、APIをたとえばLINEで公開されているものを使用すれば、自分のプログラムでLINEのアプリを操作することもできる。(メッセージを送るなど)

また、もし自分でドメインやサーバー契約をしているのであれば、自分のサーバー上のファイルに独自のAPIを作成することもできる。

参考ページ