【Swift】APIキーをschemeごとに分けつつ、パブリックなリポジトリにキーを公開したくない時

2017/10/08 追記：@motoshima1150さんよりご指摘いただいたplistの注意事項について追記しました。重要なご指摘ありがとうございます。

# やりたいこと

- 外部サービスのAPIキーなどの設定値を`debug`と`release`のschemeごとに分けたい
- APIキーなどの設定値を使用するプロジェクトはパブリックなリポジトリなので、APIキーは公開されないようにしたい

この2点を満たせるように設定していきたいと思います。
まずはそれぞれの方法を個別に見ていき、最終的にそれらの方法を組み合わせる形で説明していきます。


# APIキーをパブリックリポジトリに公開させない

APIキーなどの設定値をプロジェクトから切り離す方法として、ググると`cocoapods-keys`の利用がよく見つかりました。

早速試してみましたが、この方法ではなぜかうまくいかず、、
とりあえずパブリックリポジトリに設定値が公開されなければOKなので、結局のところ以下の手順で実装しました。

1. `Keys.plist`を作成し、そこにプロジェクトから切り離す設定値を書く
2. `Keys.plist`をgit管理下から外す
3. プロジェクト内では`Keys.plist`から値を取得する

## 1. `Keys.plist`を作成し、そこにプロジェクトから切り離す設定値を書く

下記例のように`Keys.plist`を作成後、任意の文字列でKeyを設定し（`myAPIKey`の部分）、Valueにはプロジェクトで使用する設定値を記入します。（`myapikeymyapikeymyapikey`の部分）
![スクリーンショット 2016-02-28 11.28.28.png](https://qiita-image-store.s3.amazonaws.com/0/11914/70cf4de1-c078-c4ed-ec0e-f35b385cdb2d.png)


## 2.`Keys.plist`をgit管理下から外す

`.gitignore`ファイルに`Keys.plist`を追記しましょう。

```:.gitignore
Keys.plist
```

これを忘れると結局公開されます。


## 3.プロジェクト内では`Keys.plist`から値を取得する

最後に、1で設定した設定値をプロジェクトから取得して使用する方法について説明します。

まず、簡単に`Keys.plist`から値を取得するために`KeyManager.swift`というstructを定義しておきます。

```swift:KeyManager.swift
import Foundation

struct KeyManager {
    
    private let keyFilePath = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist")
    
    func getKeys() -> NSDictionary? {
        guard let keyFilePath = keyFilePath else {
            return nil
        }
        return NSDictionary(contentsOfFile: keyFilePath)
    }
    
    func getValue(key: String) -> AnyObject? {
        guard let keys = getKeys() else {
            return nil
        }
        return keys[key]
    }
    
}
```

これで、プロジェクト内では以下のように使用できます。

```swift
if let myAPIKey = KeyManager().getValue("myAPIKey") as? String { // "myAPIKey"は自分で設定したKeys.plistのキー
    print(myAPIKey)
}
```

現状、1人で一台のMacで開発をしているので、このやり方で特に不自由ないですが、他の人と作業をするような場合はgit管理下にない`Keys.plist`を別の形で共有しないといけないという手間は出てきます。

**※2017/10/08追記**
[こちらのコメント](https://qiita.com/taji-taji/items/85ffa52a575147c03017#comment-fd82bb97b4915a182d36)にてご指摘いただいたように、plistはリソースなので公開後のアプリから中身を見ることができてしまいます。
なので、基本的には外部から見られてはいけない内容を直接書いてしまうことは避けたほうが良いです。

こちらも併せてご参考ください。

- https://qiita.com/mafmoff/items/8635d5b0c82b6af60b29
- https://qiita.com/Saayaman/items/c50000e6103358c8c4d6#comment-d518cbe570693cab32bb


# 外部サービスのAPIキーを`debug`と`release`のschemeごとに分けたい

次に、外部サービスのAPIキーのような設定値をschemeごとに分ける方法を見ていきます。

私の場合は、Flurryのアプリケーション登録を`release`と`debug`で分けたかったので、それぞれのAPIキーを取得し、schemeによってAPIキーを使い分けられるようにしています。

こちらは下記のクラスメソッドさんの記事を参考にさせていただき、`User-Defined`を使用した方法で実現しました。

[[iOS] User-Definedを利用しDebugとReleaseで定義を分ける](http://dev.classmethod.jp/smartphone/iphone/ios-user-defined/)

やり方は参考記事そのままなので、割愛します。

ちなみに、Objective-Cのマクロ？のような書き方も検討したのですが、個人的にあまりコードがきれいに見えないのでこちらにしました。

# 前述の２点を組み合わせる

最後に、前述の`Keys.plist`を使った方法と`User-Defined`を使った方法を組み合わせて、表題の要望を実現させます。

概要としては、以下のような流れになります。

【設定方法】

1. `Keys.plist`に`debug`時に使用する設定値と`release`時に使用する設定値を定義
- `User-Defined`の`debug`,`release`それぞれに`Keys.plist`で取得する**`Key`の文字列**を定義（設定値のAPIキーなどではなく、plistのKey）
- `Info.plist`に`User-Defined`の値を取得するためのKey-Valueを定義

【使用方法】

- `Info.plist`から`User-Defined`の値を取得
- `User-Defined`で取得した値を`Keys.plist`の`Key`として、設定値を取得



それでは、それぞれについて見ていきます。

## 設定方法

### 1. `Keys.plist`に`debug`時に使用する設定値と`release`時に使用する設定値を定義

下記画像では、例として

- `release` scheme で使用する `MyAPIKeyRelease`
- `debug` scheme で使用する `MyAPIKeyDebug`

の２つのKeyとそれに対応するValueを定義しました。

![スクリーンショット 2016-02-28 22.16.59.png](https://qiita-image-store.s3.amazonaws.com/0/11914/4043cbb5-0e85-777f-06f4-9189e3d38768.png)

また、前述の通り、この`Keys.plist`はgit管理下から外しておきます。

### 2. `User-Defined`の`debug`,`release`それぞれに`Keys.plist`で取得する**Keyの文字列**を定義

`User-Defined`に任意の名前（例では`MyAPIKey`）で定義を加えます。
そして、`Debug`と`Release`のそれぞれのvalueに、前工程で`Keys.plist`に設定したKeyを定義します。

![スクリーンショット_2016-02-28_22_26_43.png](https://qiita-image-store.s3.amazonaws.com/0/11914/8a0804bb-06e7-4d79-dfc6-18852aa8b19b.png)

### 3. `Info.plist`に`User-Defined`の値を取得するためのKey-Valueを定義

最後に、`Info.plist`に任意のKey（下の例では`MyAPIKey`）をつけ、
そのValueとして`$(User-Definedでつけた名前)`（例では`$(MyAPIKey)`）を定義します。

![スクリーンショット 2016-02-28 22.33.39.png](https://qiita-image-store.s3.amazonaws.com/0/11914/003312ab-36c3-4bf3-57ba-63fec5ecf318.png)

ここまでで、設定は完了です。

## 使用方法

使用方法はまずコードで示してみます。
記事前半で作成した`KeyManager`のstructを使用しています。

```swift
if let keyString = NSBundle.mainBundle().objectForInfoDictionaryKey("MyAPIKey") as? String, myAPIKey = KeyManager().getValue(keyString) as? String {
    print(myAPIKey)
    // release scheme の時は myapikeyreleasemyapikeyrelease
    // debug scheme の時は myapikeydebugmyapikeydebug
}
```

細かく見てみると、

```swift
let keyString = NSBundle.mainBundle().objectForInfoDictionaryKey("MyAPIKey") as? String
```

の部分で`Info.plist`からschemeに応じた`User-Defined`の`MyAPIKey`の値が取得されます。
仮にschemeが`debug`だとしたら、この時点で`MyAPIKeyDebug`という文字列が取得できます。

次に、

```swift
myAPIKey = KeyManager().getValue(keyString) as? String
```

の部分で、先ほど取得した値（schemeが`debug`の場合、`keyString`に`MyAPIKeyDebug`が代入されている）をKeyとして`Keys.plist`から目的のAPIキーを取得しています。

以上の方法で、表題の目的は解決できました。

# 最後に

正直なところ、少し回りくどいのでこれがベストなのかは分かりません、、

また、「`Info.plist`に書いた定義は`Keys.plist`に書く方がいいかなぁ」とか、「KeyManagerのgetValue、staticでいいかも」とか、
いろいろと思うところはありますが、大筋はこんな感じでやりたいことが実現できたかと思います。

もしいいやり方をご存知の方いらっしゃいましたらご教授ください :raised_hands: