【Swift】Alamofireの導入と使い方!HTTP通信とAPI

【Swift】Alamofireの導入と使い方!HTTP通信とAPI

この記事からわかること

  • SwiftAlamofire導入方法
  • HTTP通信やり方
  • API操作するには?
  • AFSessionクラス使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

公式ドキュメント:Alamofire

Alamofireとは?

【Swift】Alamofireの導入と使い方!HTTP通信とAPI

AlamofireとはSwiftで使用されるHTTPネットワーキングライブラリです。ネットワーク通信に関わるリクエスト/レスポンス処理だけでなく、ファイルのアップロード、非同期処理、JSONパラメータのエンコーディング、認証機能などさまざまな特徴を持っています。

アプリでの使い所としてはAPI通信(Qiitaの記事やGitHubのリポジトリの取得)などによく活用されています。

ちなみにAlamofireでアラモファイアと読み、言葉の由来はテキサス州の公式州花になっている花の名前のようです。

Alamo Fire Lupinus texensis (マメ科)

APIとは?

そもそもAPIとは「Application Programming Interface」の略称でアプリやプログラム、Webサービス同士を繋ぐインターフェースのことを指します。

サービス同士を繋ぐ形で提供されているものはAPIと呼ばれますが今回はWeb上からJSON形式でデータを受け取り、アプリ内で参照するために使用するようなAPIを使用していきます。

Xcodeでの導入

Alamofireは普通のライブラリ同様にCocoaPodsやSwift Package Managerなどのツールを利用してXcodeに導入することが可能です。

CocoaPods


pod 'Alamofire'

Swift Package Manager

https://github.com/Alamofire/Alamofire.git
【Swift】Alamofireの導入と使い方!HTTP通信とAPI

Xcodeにライブラリを導入できたら使用するファイルの上部でimportしておきます。

import Alamofire

APIの取得方法

では実際にHTTPリクエストを送信し、APIからデータを取得してみます。今回は当サイト「Webエンジニア学習部屋」の記事を全て取得するAPIにアクセスしてみたいと思います。

上記のURLを叩いて取得できるのはJSON形式の文字の羅列です。これを任意の形に変換することでアプリ内から操作しやすいようにしていけばOKです。今回の変換部分はAlamofireとは関係ないので通信部分に焦点を当てて実装してみます。

import SwiftUI
import Alamofire

struct AlamofireView: View {
    
    func getArticles(){
        AF.request("https://appdev-room.com/api/article").response { response in
                do {
                    let articles = try JSONSerialization.jsonObject(with: response.data!, options: []) as? Array<Any>
                    print(articles)
                } catch {
                    print(error.localizedDescription)
                }
            }
        
    }
    
    var body: some View {
        Button {
            getArticles()
        } label: {
            Text("get")
        }
        
    }
}

AF(Session.default)

AFの定義を見てみると実態はdefaultプロパティで定義されたシングルトンのSessionクラスになっています。

public let AF = Session.default
open class Session {
    /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified.
    public static let `default` = Session()
}

requestメソッド

リクエストを送信するのはrequestメソッドです。引数にURLやHTTPメソッド、パラメータを渡すことで任意のリクエストを送信することが可能になっています。


open class Session {
  open func request(_ convertible: URLConvertible,
                      method: HTTPMethod = .get,
                      parameters: Parameters? = nil,
                      encoding: ParameterEncoding = URLEncoding.default,
                      headers: HTTPHeaders? = nil,
                      interceptor: RequestInterceptor? = nil,
                      requestModifier: RequestModifier? = nil) -> DataRequest {
        let convertible = RequestConvertible(url: convertible,
                                            method: method,
                                            parameters: parameters,
                                            encoding: encoding,
                                            headers: headers,
                                            requestModifier: requestModifier)

        return request(convertible, interceptor: interceptor)
    }
}

HTTPメソッドを切り替える

requestメソッドの引数にはHTTPMethod型でGETやPOSTなどのHTTPメソッドを指定することができます。

// GET
AF.request(url, method: .get)
// POST
AF.request(url, method: .post)

パラメータ(データ)を渡す

APIでよくあるのがリクエストにJSONデータなどを渡して情報を登録したりする方法だと思います。AlamofireではParameters型([String: Any]型のエイリアス)のキーバリューペアでデータを渡すことが可能です。

GETを指定してパラメータを渡すとクエリで?key1=value1&key2=value2のように指定した意味になるようです。

let parameters: [String: Any] = ["key1": "value1", "key2": "value2"]

AF.request(url, method: .get, parameters: parameters).response() { response in }

POSTを指定してパラメータを渡すとHTTPBodyにJSONエンコードして含めるように指定した意味になるようです。

let parameters: [String: Any] = ["key1": "value1", "key2": "value2"]

AF.request(url, method: .post, parameters: parameters)

Headerを追加する

HTTP通信のHeaderを追加したい場合はHTTPHeaders型で渡すことが可能です。例えば認証用のBearerトークンを渡したい場合は以下のようになります。

let headers: HTTPHeaders = ["Authorization": "Bearer your_token"]

AF.request(url, method: .get, parameters: parameters, headers: headers).response() { response in }

responseメソッド

HTTP通信の結果を受け取るにはresponseメソッドを使用します。クロージャーの引数からAFDataResponse<Data?>型でレスポンスを取得することが可能です。

AF.request(url).response { response in
        do {
            let articles = try JSONSerialization.jsonObject(with: response.data!, options: []) as? Array<Any>
            print(articles)
        } catch {
            print(error.localizedDescription)
        }
    }

また引数queueでは実行スレッドを明示的に指定することも可能です。

.response(queue: DispatchQueue.global(qos: .background)) { response in }

画像ファイルなどを送信する

データではなく画像ファイルなどを送信したい場合はuploadメソッドを使用します。引数multipartFormDataのクロージャからMultipartFormData型を取得しそこにappendする形で画像ファイルを指定します。

appendする際にはwithNameサーバー側で取得するキー名mimeType形式を指定し、HTTPメソッドはPUTを指定します。

AF.upload(
    multipartFormData: { multipartFormData in       
        // 画像を少し圧縮して追加
        if let imageData = image.jpegData(compressionQuality: 0.8) {
            multipartFormData.append(
                imageData,
                withName: "file", // サーバー側で取得するキー
                fileName: "fineName.jpg",
                mimeType: "image/jpeg"
            )
        }
    },
    to: url,
    method: .put
).response() { response in }

例えばバックエンド側をLaravelで実装している場合は$request->fileから画像を取得することができます。

if ($request->file !== null) {
  // 画像を保存するなど
}

Laravelで画像を保存するなら「Intervention Image」が便利です。

画像ファイルと一緒にデータも送信する

画像ファイルと一緒にパラメータにデータを渡したい場合はいくつか注意点があります。multipartFormDataに対象のデータをData型にキャストして追加してみたのですがバックエンド側(Laravelでは$request)からは取得することができませんでした。

解決方法

色々試した結果$requestに格納されるようにするためにはPOST通信にする必要があったようです。そして画像も一緒に送るためにはX-HTTP-Method-OverridePUTを指定することで画像とデータを一緒に送信することができるようになりました。

let headers: HTTPHeaders = [
    "Content-Type": "multipart/form-data",
    "X-HTTP-Method-Override": "PUT"
]

AF.upload(
    multipartFormData: { multipartFormData in
        parameters.forEach { key, value in
            let stringValue: String?
            // 送信対象のパラメータを構築
            switch value {
            case let string as String:
                stringValue = string
            case let int as Int:
                stringValue = "\(int)"
            case let bool as Bool:
                stringValue = bool ? "true" : "false"
            default:
                stringValue = nil
            }
            
            if let data = stringValue?.data(using: String.Encoding.utf8) {
                multipartFormData.append(data, withName: key)
            }
        }
      
        if let imageData = image.jpegData(compressionQuality: 0.8) {
            multipartFormData.append(
                imageData,
                withName: "file", // サーバー側で取得するキー
                fileName: "fineName.jpg",
                mimeType: "image/jpeg"
            )
        }
      
    },
    to: url,
    method: .pos,
    headers: headers
).response() { response in }

JSON形式のデコーティング

取得したJSON形式のデータは必要に応じてSwiftで扱いやすいデータ型に変換します。先ほどはJSONSerializationクラスを使用して配列型に変換することで出力していました。

let articles = try JSONSerialization.jsonObject(with: response.data!, options: []) as? Array<Any>

構造体(クラス)に変換する

辞書型ではなく、構造体(orクラス)に変換するにはJSONDecoderクラスを利用します。詳細は以下の記事を参考にしてください。

構造体に変換するポイント

変換対象となるモデル構造体(orクラス)を先に定義しておきます。

モデル構造体の作成

struct Article: Decodable{
    let id:Int
    let title:String
}

JSONDecoderクラスによる変換

AF.request("https://appdev-room.com/api/article")
.responseData { response in
    do {
        let decoder: JSONDecoder = JSONDecoder()
        let articles: [Article] = try decoder.decode([Article].self, from: response.data!)
        print(articles)
    } catch {
        print(error.localizedDescription)
    }
}

SwiftオリジナルのHTTP通信クラス

ライブラリではなくSwiftに既に組み込まれているURLSessionクラスでもHTTP通信が可能になっています。

Alamofireを使用した処理もURLSessionクラスを用いて以下のように記述することも可能です。

func getArticleAPI() {
              

    let urlString = "https://appdev-room.com/api/article"
    // 有効なURLかをチェック
    if validationUrl(urlString: urlString) == false {
        return
    }
    guard let url = URL(string: urlString) else {
        return
    }
    // リクエストを構築
    let request = URLRequest(url: url)
    
    // URLにアクセスしてレスポンスを取得する
    URLSession.shared.dataTask(with: request) { data, response, error in
    
        if let data = data {
            do {
                let articles = try JSONSerialization.jsonObject(with: data, options: []) as? Array<Any>
                print(articles)
            } catch {
              print(error.localizedDescription)
            }
        } 
    }.resume()
    
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index