Amazon API Gateway の API のクライアント SDK を生成して iOS アプリから呼び出す

2015.07.11

API のクライアントの SDK を生成して呼び出す

Amazon API Gateway (以下 API Gateway) はマネージドな API を簡単に作成することが出来る素晴らしいサービスです。

API Gateway の機能はそれだけに留まらず、API クライアントの SDK を iOS や Android、Javascript 向けに生成する機能も持っています。これを使うと非常に簡単に API クライアントまでも実装することができます。

今回はこの機能を使って、API Gateway で作成した API のクライアントを iOS 向けに生成し、iOS アプリに組み込んで呼び出すところまでをやってみたいと思います。

API の作成

今回は OpenWeatherMap の Weather API をラップした API を作り、この API のクライアントを作ります。API の作成手順は以下のブログにまとめてありますので、こちらの手順通りに作成してください。

Model の作成

API のレスポンスをクライアントで受け取るには、API のレスポンスボディとして渡される JSON オブジェクトをパースする必要がありますが、API Gateway には Model を定義しておくことにより、その Model のクラスを各種 SDK 向けに生成する機能があります。この Model クラスも含めて、API クライアントの SDK としてまとめて生成することができます。

ということで Model を作成します。API Gateway のヘッダーのメニューの一番右側のドロップダウンから「Models」を選択します。

api-gateway-ios01

左側に表示されているのは Model 一覧です。デフォルトで ErrorEmpty というモデルが作成されています。ここに Weather API のレスポンスの Model を作成しましょう。「Create」をクリックします。

api-gateway-ios02

右側に新規作成フォームが表示されます。Model name は Model の名前を入力します。ここでは Weather とします。Content type はレスポンスボディとして返すときのフォーマットで、レスポンスヘッダの Content-Type になります。通常、application/json で良いでしょう。Model description は任意項目ですので、必須ではありません。

api-gateway-ios03

一番大事な設定が、一番下にある Model schema です。ここには Model の定義を JSON Schema 形式で記述します。JSON Schema は、JSON のデータ構造を記述するための書式のことです。JSON のデータに含まれる値のデータ型や説明などを記述することができます。日本語では、以下の記事が詳しいです。

ということで、Model schema を用意する必要があります。といっても一から自力で記述する必要はありません。以下のツールを使うと、実際の JSON データから JSON Schema を生成することができます。

左上の JSON に実際の JSON データを入れて「Generate Schema」をクリックすると右側に JSON Schema が生成されます。Force required がデフォルトで有効になっていますが、今回の Model 作成には不要なので無効にしておきます。

api-gateway-ios04

OpenWeatherMap の Weather API の実際のレスポンスボディは以下の様な感じです。これをインプットとして JSON Schema を生成します。

{
  "coord": {
    "lon": 139.69,
    "lat": 35.69
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "Sky is Clear",
      "icon": "01n"
    }
  ],
  "base": "cmc stations",
  "main": {
    "temp": 298.43,
    "pressure": 1011,
    "humidity": 83,
    "temp_min": 294.82,
    "temp_max": 300.93
  },
  "wind": {
    "speed": 5.7,
    "deg": 190
  },
  "clouds": {
    "all": 0
  },
  "dt": 1436613100,
  "sys": {
    "type": 1,
    "id": 7619,
    "message": 0.0048,
    "country": "JP",
    "sunrise": 1436556845,
    "sunset": 1436608744
  },
  "id": 1850147,
  "name": "Tokyo",
  "cod": 200
}

JSON Schema に変換すると、以下のようになりました。ちょっと長くなってしまってすみません…。

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id": "/",
  "type": "object",
  "properties": {
    "coord": {
      "id": "coord",
      "type": "object",
      "properties": {
        "lon": {
          "id": "lon",
          "type": "number"
        },
        "lat": {
          "id": "lat",
          "type": "number"
        }
      }
    },
    "weather": {
      "id": "weather",
      "type": "array",
      "items": {
        "id": "0",
        "type": "object",
        "properties": {
          "id": {
            "id": "id",
            "type": "integer"
          },
          "main": {
            "id": "main",
            "type": "string"
          },
          "description": {
            "id": "description",
            "type": "string"
          },
          "icon": {
            "id": "icon",
            "type": "string"
          }
        }
      }
    },
    "base": {
      "id": "base",
      "type": "string"
    },
    "main": {
      "id": "main",
      "type": "object",
      "properties": {
        "temp": {
          "id": "temp",
          "type": "number"
        },
        "pressure": {
          "id": "pressure",
          "type": "integer"
        },
        "humidity": {
          "id": "humidity",
          "type": "integer"
        },
        "temp_min": {
          "id": "temp_min",
          "type": "number"
        },
        "temp_max": {
          "id": "temp_max",
          "type": "number"
        }
      }
    },
    "visibility": {
      "id": "visibility",
      "type": "integer"
    },
    "wind": {
      "id": "wind",
      "type": "object",
      "properties": {
        "speed": {
          "id": "speed",
          "type": "number"
        },
        "deg": {
          "id": "deg",
          "type": "integer"
        }
      }
    },
    "clouds": {
      "id": "clouds",
      "type": "object",
      "properties": {
        "all": {
          "id": "all",
          "type": "integer"
        }
      }
    },
    "dt": {
      "id": "dt",
      "type": "integer"
    },
    "sys": {
      "id": "sys",
      "type": "object",
      "properties": {
        "type": {
          "id": "type",
          "type": "integer"
        },
        "id": {
          "id": "id",
          "type": "integer"
        },
        "message": {
          "id": "message",
          "type": "number"
        },
        "country": {
          "id": "country",
          "type": "string"
        },
        "sunrise": {
          "id": "sunrise",
          "type": "integer"
        },
        "sunset": {
          "id": "sunset",
          "type": "integer"
        }
      }
    },
    "id": {
      "id": "id",
      "type": "integer"
    },
    "name": {
      "id": "name",
      "type": "string"
    },
    "cod": {
      "id": "cod",
      "type": "integer"
    }
  }
}

API Gateway の設定画面に戻り、Model schema に貼り付けて「Create model」をクリックして終わりです。

api-gateway-ios05

Method Response の設定

Model が出来上がったら、レスポンスに設定します。Method Execution 画面に移動して Method Response をクリックします。

api-gateway-ios06

HTTP Status200 が表示されているので、これを展開すると Response Models for 200 セクションの中に一つだけ Model が設定されていると思います。デフォルトでは Empty が設定されているので、これを Weather に変更します。最後にチェックマークをクリックして終わりです。

api-gateway-ios07

Method Response の設定が終わったら、デプロイを行います。「Deploy API」をクリックします。Method Execution から選ぶとデプロイできないバグがあるので、Resource 一覧 または Method 一覧から行ってください(分かりやすいバグなので、きっと近いうちに直ると思います)。

api-gateway-ios08

SDK の生成

デプロイが終わったらいよいよ SDK を生成します。デプロイが完了すると Stage の 設定画面が表示されていると思いますので、SDK Generation タブをクリックします。

Platform は iOS、Android、JavaScript から選べます。今回は iOS を選択します。Prefix に設定した文字列は、生成する各クラスの Prefix になります。今回は CLI としました。

api-gateway-ios09

ここまでできたら「Generate SDK」をクリックし、Zip ファイルをダウンロードします。

Xcode プロジェクトへの組み込み

次に、生成した SDK を Xcode プロジェクトに組み込みましょう。なお、Xcode プロジェクトは作成済みの前提で進めさせていただきますので、作成しておいてください。

まず Xcode プロジェクトに API Gateway の SDK である AWSAPIGateway を CocoaPods 経由でインストールしておきましょう。

pod 'AWSAPIGateway'
$ pod install
Analyzing dependencies
Downloading dependencies
Installing AWSAPIGateway (2.2.1)
Installing AWSCore (2.2.1)
Generating Pods project
Integrating client project

次に、生成したファイルを組み込みましょう。ダウンロードした Zip ファイルを解凍すると次のようなフォルダ構成になっています。

api-gateway-ios10

この中の「generated-src」フォルダに、Client クラスと Model クラスのヘッダファイルとソースファイルが入っているのですべてドラッグアンドドロップで Xcode プロジェクトに追加します。

api-gateway-ios11

ここで、生成したクラスを軽く覗いてみましょう。Model のルートにあたる CLIWeather クラスは以下のような感じです。iOS では JSON オブジェクトのマッピングに Mantle が利用されています。

#import <Foundation/Foundation.h>
#import <AWSCore/AWSCore.h>
#import "CLIWeather_coord.h"
#import "CLIWeather_main.h"
#import "CLIWeather_sys.h"
#import "CLIWeather_wind.h"
#import "CLIWeather_weather_item.h"
#import "CLIWeather_clouds.h"

 
@interface CLIWeather : AWSModel

@property (nonatomic, strong) CLIWeather_coord *coord;


@property (nonatomic, strong) NSArray *weather;


@property (nonatomic, strong) NSString *base;


@property (nonatomic, strong) CLIWeather_main *main;


@property (nonatomic, strong) NSNumber *visibility;


@property (nonatomic, strong) CLIWeather_wind *wind;


@property (nonatomic, strong) CLIWeather_clouds *clouds;


@property (nonatomic, strong) NSNumber *dt;


@property (nonatomic, strong) CLIWeather_sys *sys;


@property (nonatomic, strong) NSNumber *_id;


@property (nonatomic, strong) NSString *name;


@property (nonatomic, strong) NSNumber *cod;


@end
#import "CLIWeather.h"

@implementation CLIWeather

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"coord": @"coord",
             @"weather": @"weather",
             @"base": @"base",
             @"main": @"main",
             @"visibility": @"visibility",
             @"wind": @"wind",
             @"clouds": @"clouds",
             @"dt": @"dt",
             @"sys": @"sys",
             @"_id": @"id",
             @"name": @"name",
             @"cod": @"cod"
             };
}

+ (NSValueTransformer *)weatherJSONTransformer {
	return [NSValueTransformer awsmtl_JSONArrayTransformerWithModelClass:[CLIWeather_weather_item class]];
}

@end

API を呼び出してみる

それでは呼び出してみましょう。API 呼び出すには CLIWeatherAPIClient クラスを使います(API のクライアントのクラスは XXXAPIClient という形式になります)。weatherGet というメソッドが用意されているので、このメソッドをコールします。Bolts を利用した非同期処理で実装されている (BFTaskAWSTask になっています) ので、weatherGet の戻り値は AWSTask オブジェクトになります。continueWithExecutor:withBlock: を呼び出すとコールバックを受け取れます。

#import "ViewController.h"
#import "CLIWeatherAPIClient.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    CLIWeatherAPIClient *client = [CLIWeatherAPIClient defaultClient];
    [[client weatherGet:@"London"] continueWithExecutor:[AWSExecutor mainThreadExecutor]
                                             withBlock:^id(AWSTask *task) {
        if (task.error) {
            NSLog(@"Error: %@", task.error);
        } else if (task.exception) {
            NSLog(@"Exception: %@", task.exception);
        } else {
            CLIWeather *weather = task.result;
            CLIWeather_weather_item *item = weather.weather.firstObject;
            NSString *message = [NSString stringWithFormat:@"Weather: %@", item.main];
            [[[UIAlertView alloc] initWithTitle:@"Get response"
                                       message:message
                                      delegate:nil
                             cancelButtonTitle:nil
                             otherButtonTitles:@"Close", nil] show];
        }
        return nil;
    }];
}

@end

実行してみましょう。レスポンスを受け取ることができました!

api-gateway-ios12

まとめ

生成された SDK の使いかたは、AWS Mobile SDK とほぼ変わらないので、触れたことがあるかたは使いやすいものとなっています。API Gateway で作られる API は AWS の特別なものではないので、HTTP クライアントに別なライブラリ (AFNetworking とか) を使用している場合は SDK の生成機能を必ずしも使う必要はありません。

なお、今回は Public な API として設定しましたが、この方法以外に API キーを利用する方法や IAM を利用する方法などもあります。これらの利用方法は別途ご紹介できればと思います。

参考