Amazon Mobile SDK for iOSを使って感じたこととはまったこと


2014年の7月にiOSなどのモバイル用に新しいAWS SDKが発表されました。
それが「AWS Mobile SDK」です。

今までもSDKは存在していたので、V2といったところです。

発表から半年ほど経っていますが、
このSDKを使う機会が最近ありましたので、
そのときのことをまとめてみました。

## 参考にしていた情報
### AWS Mobile SDK関連
* [AWS Mobile SDK の発表ブログ(AWS Blog)](http://aws.typepad.com/aws_japan/2014/07/aws-mobile-services.html)
* [AWS Mobile SDK のドキュメント](http://docs.aws.amazon.com/mobile/sdkforios/developerguide/cognito-auth.html)
* [AWS Mobile SDK のサンプル](https://github.com/awslabs/aws-sdk-ios-samples)

### Cognito関連
* [Cognito 公式ページ](http://aws.amazon.com/jp/cognito/)
* [Cognite 発表資料](http://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-amazon-cognito-amazon-mobile-analytics)
* [クラスメソッドによるCognite 解説記事](http://dev.classmethod.jp/referencecat/cognito/)

## V2の改善点

V2になることで大きく改善がされました。

個人的に以下の点がありがたいと思っています。
### AWSリソースアクセスの認証処理が簡単に実装できるようになった
DynamoDBやS3といったAWSリソースにアクセスするために、
一時的な証明書を取得する必要があります。
今までは、そのキーを取得するために、
Token Vending Machine(TVM)とそれ用のクライアント処理を実装する必要がありました。
どちらもサンプルを参考にすればできるのですが、
アカウントの管理など自分達でやる必要もあるため正直かなり面倒でした。

ところが、今回「Cognito」という仕組みが出てきました。

クライアント側の実装のみで、簡単に一時的な証明書を取得できます。
さらに、新しいSDKは、
AWSリソースへのアクセス時には、その証明書を裏で取得してくれているので、
気にすることなくアクセスできます。

### 非同期処理
今までのSDKだと、自分たちで非同期処理を実装し、
値が取得できたらメインスレッドに戻すといったことが必要でした。

今回は、そういったことが不要になりました。
(正確には、Bolts Frameworkを使って非同期処理を書けるようにしている)

Bolts Frameworkについては以下を参照
[iOS/Android対応 Boltsを使って非同期処理を統一的に書く](http://qiita.com/yuch_i/items/fbb970b1131b8a25b690)

### DynamoDBの呼び出し方が簡単になった (Object Mapper)
今までDynamoDBのAPI実行において、
リクエスト生成とレスポンスからモデルオブジェクトの生成が面倒でした。
今回から、AWSDynamoDBModelを継承したクラスを用意しておけば、
リクエスト生成やモデルオブジェクトの生成をObject Mapperがやってくれるので楽ちんです。

``` Objective-C
#import "AWSDynamoDBObjectMapper.h"
#import "DynamoDB.h"

@interface DDBUserProfileTableRow : AWSDynamoDBObjectModel <AWSDynamoDBModeling>

// カラム名をここで定義しておく
@property (nonatomic, strong) NSString *userId;
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *userPictUrl;
@property (nonatomic, strong) NSNumber *createDate;

@end
```

``` Objective-C
#import "DDBUserProfileTableRow.h"

@implementation DDBUserProfileTableRow

// テーブル名
+ (NSString *)dynamoDBTableName {
    return @"UserProfile";
}

// ハッシュキー
+ (NSString *)hashKeyAttribute {
    return  @"userId";
}

@end
```

``` Objective-C
// ユーザー情報取得
+ (void)loadUserProfileByUserId:(NSString *)userId
{
    [[[AWSDynamoDBObjectMapper defaultDynamoDBObjectMapper] load:[DDBUserProfileTableRow class]
                                                         hashKey:userId
                                                        rangeKey:nil]
     continueWithExecutor:[BFExecutor mainThreadExecutor] 
     　　　　　　 withBlock:^id(BFTask *task) {
         // 非同期処理
         if (!task.error) {
             // 成功処理
             // データがセットされた状態モデルオブジェクトが取得できる
             DDBUserProfileTableRow *tableRow = (DDBUserProfileTableRow *)task.result;
             // もし、対象のレコードがない場合、モデルオブジェクトの全ての値にnilが入っている
         } else {
             // 失敗処理
         }
         return nil;
    }];
    
}
```

## 作ったもの
* Facebookでログイン
* DynamoDBにユーザー名とユーザー画像URLを保存
* 自動ログイン
* DynamoDBからユーザー名とユーザー画像URLを取得
* ログアウト

## はまったこと
### リージョンがCognitoとDynamoDBで違い、Cognito Syncが使えない
Cogniteはまだ以下のリージョンしか対応していません
* US East (N. Virginia)
* EU (Ireland)

しかし、DynamoDBはTokyoリージョンだったりしますよね。
Cognitoが限られたリージョンのみだからか、
SDKはそれを想定した作りになっています。
以下のように
Cognitoとそれ以外のAWSサービスのリージョンを別で指定できるようになっています。

``` Objective-C
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Cognitoを使った一時的な証明書を取得 (※Cognite用リージョンはここで指定)
    AWSCognitoCredentialsProvider *credentialsProvider
    = [AWSCognitoCredentialsProvider credentialsWithRegionType:CognitoRegionType
                                                identityPoolId:CognitoIdentityPoolId];
                                                
    // 自動的に発行されるIDを取得
    [[credentialsProvider getIdentityId] continueWithSuccessBlock:^id(BFTask *task) {
        NSLog(@"ID=%@", credentialsProvider.identityId);
        return nil;
    }];

    // AWSサービスにアクセスする場合の設定(※ Cognite以外のAWSサービス用リージョンはここで指定)
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration 
    = [AWSServiceConfiguration configurationWithRegion:DefaultServiceRegionType
                                   credentialsProvider:credentialsProvider];

    return YES;
}
```

ところが、なぜかCognitoデータ同期を使うときに、リージョンがその他AWSサービス用リージョンになっていました。

``` Objective-C
- (void)updateIsRegisteredUser:(BOOL)isRegisterdUser
{
    AWSCognito *syncClient = [AWSCognito defaultCognito];
    AWSCognitoDataset *dataset = [syncClient openOrCreateDataset:AWSCongniteDataSetKeyState];
    
    // 保存
    NSString *value = (isRegisterdUser) ? @"YES" : @"NO";
    [dataset setString:value forKey:AWSCongniteDataSetKeyStateIsRegisterd];
    
    // 同期
    [[dataset synchronize] continueWithBlock:^id(BFTask *task) {
        if (task.isCancelled) {
            DEBUG_LOG(@"同期キャンセル");
        } else if (task.error) {
            DEBUG_LOG(@"同期エラー: %@",task.error);
        } else {
            DEBUG_LOG(@"同期成功");
        }
        return nil;
    }];
}
```

そこで、AWSCognitoインスタンス生成時にリージョン指定をすることで解決しました。

``` Objective-C
- (void)updateIsRegisteredUser:(BOOL)isRegisterdUser
{
    // self.cognitoServiceConfigは事前にCognito用のリージョンを指定して作成したAWSServiceConfigurationです
    AWSCognito *syncClient = [[AWSCognito alloc] initWithConfiguration:self.cognitoServiceConfig];
    AWSCognitoDataset *dataset = [syncClient openOrCreateDataset:AWSCongniteDataSetKeyState];
    
    // 保存
    NSString *value = (isRegisterdUser) ? @"YES" : @"NO";
    [dataset setString:value forKey:AWSCongniteDataSetKeyStateIsRegisterd];
    
    // 同期
    [[dataset synchronize] continueWithBlock:^id(BFTask *task) {
        if (task.isCancelled) {
            DEBUG_LOG(@"同期キャンセル");
        } else if (task.error) {
            DEBUG_LOG(@"同期エラー: %@",task.error);
        } else {
            DEBUG_LOG(@"同期成功");
        }
        return nil;
    }];
}
```

### Facebook認証がシミュレーターのSafariでネットワークエラー
Facebook認証でSafariが立ち上がるのですが、
その時に以下のような表示になることがありました。

![iOS Simulator Screen Shot 2015.03.29 19.06.16.png](https://qiita-image-store.s3.amazonaws.com/0/15875/f20251bc-9b2e-1b77-b966-08215dcfa5e7.png "iOS Simulator Screen Shot 2015.03.29 19.06.16.png")

これは、シミュレーターの再起動で解消しました。

# 使ってみた感想
今回の新しいSDKにより、AWSを使ったアプリの実装がかなり楽になったと感じました。
いまいち分かっていないのが、Cognitoで保持できるデータです。
保持できる容量が限られているため、使い道が難しいなと感じています。