【AWS Mobile SDK for iOS】AWSサービスクライアントの初期化と取得方法について調べたこと

# はじめに

[AWS Mobile SDK](https://aws.amazon.com/jp/mobile/sdk/)を使えば、S3やDynamoDBといったAWSサービスをアプリで直接利用できるようになります。これはとても便利なことなのですが、実際にコーディングする時、オンラインのドキュメントだけでは使い方がなかなか分かりにくかったので、[ソースコード](https://github.com/aws/aws-sdk-ios)を読んで理解を深めた話です。
この記事ではサービスクライアントの初期化について書いています。

なお、AWS Mobile SDK for iOSのバージョンは`v2.4.14`で動作確認をしています。

---
各AWSサービスのサービスクライアントの初期化と取得には、いくつかの方法がありますが、基本的には`AWSServiceConfiguration`とサービス固有のコンフィギュレーションをセットして利用します。

例えば、DynamoDBを操作する`AWSDynamoDBObjectMapper`ならばサービス固有のコンフィギュレーションとして`AWSDynamoDBObjectMapperConfiguration`が必要です。
また、サービス固有コンフィギュレーションは不要なクライアントもあります。
ちなみに、"サービス固有コンフィギュレーション"とは公式な呼び方ではなく、僕が勝手にそう呼んでいるだけです。

# サービスコンフィギュレーションの設定方法

`AWSServiceConfiguration`をサービスクライアントに設定する方法は以下の3通りがあります。

1. Info.plistに記述する
1. `[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`にセットする
1. `registerXxxxWithConfiguration:XxxxConfiguration:forKey:`メソッドを使って設定する

## 1. Info.plistに記述する

1.の方法では、Info.plistに以下を記述します。
	
```xml
<key>AWS</key>
<dict>
	<key>CredentialsProvider</key>
	<dict>
		<key>CognitoIdentity</key>
		<dict>
			<key>Default</key>
			<dict>
				<key>PoolId</key>
				<string>YOUR COGNITO POOL ID</string>
				<key>Region</key>
				<string>YOUR REGION</string>
			</dict>
		</dict>
	</dict>
</dict>	
```
	
`AWSCore/Service/AWSInfo.m`を読むとInfo.plistの解析の仕方が分かります。この`AWSInfo`クラスは後述するインスタンスの取得時に利用されます。
	
## 2. `[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`にセットする

2.方法では、`AWSServiceManager`シングルトンを利用します。
`[AWSServiceManager defaultServiceManager]`はシングルトン取得メソッドで、この`defaultServiceConfiguration`も後述するインスタンス取得時に利用されます。

また、`[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`の実装(`AWSCore/Service/AWSService.m`)を見てみると、
	
```objc
- (void)setDefaultServiceConfiguration:(AWSServiceConfiguration *)defaultServiceConfiguration {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _defaultServiceConfiguration = [defaultServiceConfiguration copy];
    });
}
```

となっており、最初の一回しかシングルトンにセットできないことに注意したいです。

## 3. `registerXxxxWithConfiguration:XxxxConfiguration:forKey:`メソッドを使って設定する

(Xxxxには各サービス名などが入ります)

3.の方法では、`AWSServiceConfiguration`と同時にサービス固有コンフィギュレーションもセットします(おそらくほとんどのサービスクライアントで)。

---
1.と2.は各AWSサービスクライアントに共通して設定されますが、3.はサービス毎に個別に設定できます。これはインスタンスの取得方法に関係してきます。

# サービスクライアントのインスタンス取得方法

インスタンスの取得方法は基本的に以下の2通りがあります。

1. `defaultXxxx`メソッドでシングルトンを取得して使う
1. `registerXxxxWithConfiguration:XxxxConfiguration:forKey:`メソッドで生成したインスタンスを`XxxxForKey:`メソッドで取り出して使う

## 1. `defaultXxxx`メソッドでシングルトンを取得して使う

1.の方法では、まず`AWSInfo`クラスを使いInfo.plistの設定情報を読み込もうとし、情報があれば使います。

Info.plistに設定情報がなければ、`[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`を使います。これは、インスタンスを取得する前に`[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`にコンフィグをセットしておかなければいけないことを意味します。そのため、コンフィグの設定は`AppDelegate`の`application:didFinishLaunchingWithOptions:`メソッドの早い段階で実行するのが確実だと思います。

なお、Info.plistにも`defaultServiceConfiguration`にも設定情報がなければ、例外を投げるといった実装になっています。

## 2. `registerXxxxWithConfiguration:XxxxConfiguration:forKey:`メソッドで生成したインスタンスを`XxxxForKey:`メソッドで取り出して使う

2.の方法では、引数に指定したコンフィギュレーションを使うので特に難しくないと思います。指定したコンフィギュレーションでインスタンスを初期化し、指定キーと紐付けてstaticなディクショナリに格納してインスタンスを保持します。`XxxxForKey:`メソッドで指定したキーでディクショナリを引きインスタンスを取得します。

ちなみに、registerする前に`XxxxForKey:`メソッドで取り出そうとすると、Info.plistから設定情報を読み込んでインスタンスを初期化します。個人的には例外を投げて落としてくれたほうがうれしいですが。

なお、1の方法では、サービス固有コンフィギュレーションは(おそらく多くの場合は)デフォルト値がインスタンス初期化時に適用されます。この場合、カスタマイズなサービス固有コンフィギュレーションはセットできないと思いましたが、`AWSDynamoDBObjectMapper`はsaveやremoveといったメソッドを実行する時にサービス固有コンフィギュレーションを渡すことができます。この辺はクライアントごとに実装を詳しく知る必要がありますね。

# まとめ

まとめると、[公式ドキュメント](http://docs.aws.amazon.com/mobile/sdkforios/developerguide/setup-aws-sdk-for-ios.html)にもあるように、`AppDelegate`の`application:didFinishLaunchingWithOptions:`メソッドで`defaultServiceConfiguration`の設定をし、サービスクライアントを使うシーンでシングルトンを取得するのが素直な使い方に思えます。

```objc
@implementation AppDelegate

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

	AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] 
		initWithRegionType:YOUR_REGION identityPoolId:YOUR_COGNITO_POOL_ID];
	    
	AWSServiceConfiguration *serviceConfig = [[AWSServiceConfiguration alloc] initWithRegion:YOUR_REGION 
		credentialsProvider:credentialsProvider];
	
	[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = serviceConfig;
	
	...
}
	
```

```objc
// DynamoDBを使う場合
AWSDynamoDBObjectMapper *serviceClient = [AWSDynamoDBObjectMapper defaultDynamoDBObjectMapper];
...

```

また、`registerXxxxWithConfiguration:XxxxConfiguration:forKey:`を使うのは個別にコンフィギュレーションをセットしたい場合でしょうか。


`[AWSServiceManager defaultServiceManager].defaultServiceConfiguration`がシングルトンで各サービスに横断的に利用されるのは便利である反面、いつどこでクライアントに設定が反映されるのか、ちょっとソースコードを読まないと分からなかったので、今回は良い勉強になりました。


ちなみに、AWS Mobile Hubのインテグレーションガイドに従うとInfo.plistに書くように誘導されがちですが、Info.plistはipaファイルから簡単に抽出できてしまうので、あまり良くないと思っています。

# 参照

- https://github.com/aws/aws-sdk-ios
- http://docs.aws.amazon.com/mobile/sdkforios/developerguide/setup-aws-sdk-for-ios.html