iOSアプリでも環境変数が使いたい！


## はじめに

GitHubにPublicなリポジトリでAPIキーとかを隠蔽したまま公開したい！っていうことありませんか。
`.env` ファイルとか作ってそれぞれの環境に合わせてビルドできるようになりたいーっていうやつ

ついでに `Info.plist` にAPIキー書くやつたまにあるけどそれもやりたいっていう

## 環境

- Xcode9.1(8.3.3でも確認済み)
- macOS 10.12.6
- swift 4.0.2

## ソースコード編

### 手順

1. Xcodeプロジェクトの作成(省略)
2. `.env` および `.env.sample` の作成

![スクリーンショット 2017-11-16 10.45.59.png](https://qiita-image-store.s3.amazonaws.com/0/97280/b77a3693-fffd-2811-c227-4ca11b816fa2.png "スクリーンショット 2017-11-16 10.45.59.png")

```:.env.sample
TWITTER_CONSUMER_KEY=''
TWITTER_CONSUMER_SECRET=''
```

こんな感じに作って、どちらとも `<PROJECT_NAME>.projectpbxproj` が読める状態にする。
3. `.gitignore` 設定

```bash
$ echo "/path/to/.env" >> /path/to/.gitignore
```

この時点で `git add .env` などしてなかったらGit管理下になっていないのでいいかと思いますが、Git管理下になっていた場合、以下

```bash
$ git rm --cached /path/to/.env
```

![スクリーンショット 2017-11-16 10.52.01.png](https://qiita-image-store.s3.amazonaws.com/0/97280/95d1f94b-290b-78ae-0fc9-748d9f01a52c.png "スクリーンショット 2017-11-16 10.52.01.png")

`git clone` とかした時点で上の画像みたいに `File NotFound` 状態になったらOK
4. 環境変数読み込み

今回は `swift4.0.2` で確認してるのでそれぞれの環境に合わせて実装等お願いします。

`AppDelegate.swift` の `func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { }` に

```swift:AppDelegate.swift
・・・
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        guard let path = Bundle.main.path(forResource: ".env", ofType: nil) else {
            fatalError("Not found: '/path/to/.env'.\nPlease create .env file reference from .env.sample")
        }
        let url = URL(fileURLWithPath: path)
        do {
            let data = try Data(contentsOf: url)
            let str = String(data: data, encoding: .utf8) ?? "Empty File"
            let clean = str.replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: "")
            let envVars = clean.components(separatedBy:"\n")
            for envVar in envVars {
                let keyVal = envVar.components(separatedBy:"=")
                if keyVal.count == 2 {
                    setenv(keyVal[0], keyVal[1], 1)
                }
            }
        } catch {
            fatalError(error.localizedDescription)
        }
        ・・・
    }
・・・
```

な感じに書く。 `.env` がなかったら `fatalError` が発生する。
メソッドぐちゃぐちゃになるので自分は別のメソッド作りました。

さっきの続きに。
今回は `TwitterKit` のAPIキーをサンプルに

```swift:AppDelegate.swift
・・・
        let env = ProcessInfo.processInfo.environment
        Twitter.sharedInstance().start(withConsumerKey: env["TWITTER_CONSUMER_KEY"]!, consumerSecret: env["TWITTER_CONSUMER_SECRET"]!)
```

`.env` の左辺をキーとして取得できるようになってたら完了！

## `Info.plist` 編

### 手順

`ソースコード編` の `4` の手順までは同じ

1. `Run Script` を作成。

![スクリーンショット_2017-11-16_11_04_29.png](https://qiita-image-store.s3.amazonaws.com/0/97280/66c6dea6-df3c-57f4-bc4f-5a29f93eb079.png "スクリーンショット_2017-11-16_11_04_29.png")

みなさんに説明するまでもなく...
この `Run Script` は `Copy Bundle Resources (○ items)` よりも上に配置するようにしないと上手く動かないです
2. `PlistBuddy` を使うスクリプトの記述

`PlistBuddy` の説明はパスで、調べれば `Build Number` をなんとかーっていうのがたくさん出てくると思います！

![スクリーンショット 2017-11-16 11.07.37.png](https://qiita-image-store.s3.amazonaws.com/0/97280/7c9a95fc-2c02-a9e9-7848-a66bec053a50.png "スクリーンショット 2017-11-16 11.07.37.png")

また、 `TwitterKit` の例ですが、URLSchemeを事前に用意しておいてそこを `twitterkit-<API_KEY>` に置き換えるといったような処理

```sh:RunScript
plistBuddy="/usr/libexec/PlistBuddy"
infoPlistFileSource="${SRCROOT}/${INFOPLIST_FILE}"
infoPlistFileDestination="${TEMP_DIR}/Preprocessed-Info.plist"

. /path/to/.env

$plistBuddy -c "Set :CFBundleURLTypes:0:CFBundleURLSchemes:0 twitterkit-${TWITTER_CONSUMER_KEY}" "${infoPlistFileDestination}"
```

`Info.plist` の特定のキーへのPATHは普通に辞書と配列でアクセスできるので説明はなしで

試しにビルドして、　`twitterkit-<API_KEY>://` のURLSchemeをSafari等でも `UIApplication.shared.canOpenURL(URL)` で行ければできてると思います。

## 最後に

ArchiveしてExportした `.ipa` ファイルを `unzip` したら `Info.plist` とかは見られるのであくまでも外部リモートに公開してるときだけ隠蔽できるという感じでした。

![Info_plist.png](https://qiita-image-store.s3.amazonaws.com/0/97280/74c7f47c-0cb3-6f14-dd35-ac5744a2eaa9.png "Info_plist.png")

今回は `.env` ファイル内の値を環境変数として読み込むだけでしたが、普通に母艦の環境変数も使えると思うので参考になればと思います。