1. Qiita
  2. 投稿
  3. Swift

SwiftでREST APIクライアントを作る

  • 32
    いいね
  • 0
    コメント

Swiftで通信といえばAlamofire等の素晴らしいライブラリが多数ありますが、実装するときにはプロジェクト毎に設計や決まりがあり、導入ライブラリが決まっていたとしても、その設計をどうするかについてはプロジェクト毎に多様です。

そこで今回はSwift3で統一したインターフェースでAPIクライアントを作成するためのライブラリを紹介したいと思います。

今回紹介するライブラリ「Gryphon」

REST API client kit for Swift

https://github.com/rinov/Gryphon

特徴

Gryphonは統一されたインターフェースのAPIクライアントを作成するためのライブラリで、以下の特徴があります。

  • Pure Swift
  • REST APIに対応しやすい
  • 型安全
  • 非同期処理を持たない
  • メソッドチェーンで簡潔に記述できる
  • 軽量である

基本はインターフェースを統一するだけのラッパーです。
では、なにが嬉しいのか?

使用例

client.swift
API.Messages.getMessage()

        .retry(max: 5)

        .interval(milliseconds: 500)

        .success { response in
            // Do something
        }

        .failure { error in
            // Do something 
        }

retryintervalなどはデフォルト機能として搭載されています。
通常、Alamofireをそのまま使った場合ではレスポンスの取得時に以下のようにswitchなどの条件分岐があるかと思います。

Alamofire.request("https://hoge.jp/")
    .responseData { response in

        switch response.result {

        case .success:
            print("Success")

        case .failure(let error):
            print(error)

        }

    }

しかしGryphonでは独立して定義されたsuccessfailureのクロージャが呼ばれるため、より処理の見通しが良くなります。

その他の恩恵としては2つのクロージャは初期化時に定義を行うため存在することが保証され、未定義な場合はコンパイラレベルでエラーが発生するため実行時にnilで落ちることがなく安全です。

GryphonでAPIを作成する場合、インターフェースは以下のようになります。

API.{機能郡名}.{機能名}

例えばユーザーに関連するエンドポイントがある場合にはこのようなイメージになります。

エンドポイント例: https://hoge.jp/users/

Gryphonを用いたネイティブでのAPI実装例:

API.Users.add()     //ユーザーの追加
API.Users.delete()  //ユーザーの削除
API.Users.patch()   //ユーザーの更新

これにより単一のAPIという窓口から操作でき、REST風なインターフェースで操作できるため開発者的にも嬉しいです。

Swift3でAlamofireを使用した例

より具体的な例としてSwift3Alamofireを使った例を紹介します。

はじめにAPIクラスを作成します。
APIにバージョニングを含む場合にはここへバージョン情報を持たせておくと良いでしょう。
その他APIの共通事項があればここに入れます。

API.swift
final class API {

    ....

}

次にAPIを拡張して機能郡を作成していきます。Gryphonでは機能郡へのアクセスを統一するためにRequestableプロトコルがあり、これに準拠したクラスを作成します。

Messages.swift
extension API {

  final class Messages: Requestable {

        // required `Requestable`
        static var baseURL: String {

            return "https://hoge.jp/"

        }

        // required `Requestable`
        static var path: String {

            return baseURL + "messages/"

        }

}

続いてリクエストを作成します。

Messages.swift
exntension Messages {

       // Task<通信に成功した時に返す型, エラー時に返す型>
       class func getMessage() -> Task<Message, Error> {

            let task = Task<Message, Error> { success, failure in

                Alamofire.request(path, method: .get, encoding: JSONEncoding.default)
                    .responseJSON(completionHandler: { response in

                        // JSONを好きな方法でデコード

                        if /*取得したデータが正常だった場合*/ {

                            let message = Message(message: result)

                            // successにMessageを渡す
                            success(message)

                        }else{

                            // failureにErrorを渡す
                            failure(ResponseError.unexceptedResponse)

                        }

                    })

            }

            return task

        }

}

TaskはAPIが成功または失敗したときに返却する型を指定します。

getMessageでは成功したときにはMessageを返却し、失敗したときにはErrorを返すように指定しています。

例えば、成功したときにはメッセージの配列、失敗したときにはステータスコードを取得したい場合には以下のように定義します。

class func getMessages() -> Task<[Message],Int>

これでAPIの完成です。あとは以下のように利用するだけです。

client.swift
API.Messages.getMessage()

         // `response`はMessage型で取得したオブジェクトが格納されています
        .success { response in

            let message: Message = response // Ok (オブジェクトマッピング済みのため)

            messages.append(message)

        }

        .failure { error in

            print(error)

        }

successerrorはTaskで指定した値が非Optionalでくるため、そのまま扱うことができます。

【応用編】 エンドポイントのスイッチング

またREST APIでは基本的にHTTPメソッドで操作しますが例えば以下のようなエンドポイントがあったとします。

http://hoge.jp/messages/ :GET
http://hoge.jp/messages/A/ :POST

このような場合は先ほどのMessageクラスの中に以下のコードを追加します。

Messages.swift
enum Router: String {
  case getMessage = ""
  case postMessage = "A/"
}

static var router: Router = .getMessage // Default value

そして各リクエスト関数の中で
router = .postMessageにようにスイッチングするコードを記述し
Requestableのプロパティであるpathで以下のように設定します。

static var path: String {

    return baseURL + "messages/" + router.rawValue

}

このようにすることでエンドポイントは常にpathを監視すれば良いことになり複数のURIに安全にスイッチングすることができます。
また、リクエスト毎にurlプロパティを追加する必要がなくAPIとURIの関係がわかりやすくなります。

最後まで読んでいただきありがとうございました。