🔨

Xcode の Build Configuration を使って API の接続情報を管理する

2022/01/04に公開

前書き

Xcode で API サーバ連携有りのアプリを開発する場合、大抵以下の要件が発生します。

  • API サーバが Local, Staging, Production の3種類あり、ビルド時に接続先を切り替えたい
    • 一時的に Staging サーバに接続する状態で Xcode で実行したりもしたい
  • API サーバに接続する際に API キーなどが必要となり、この値はリポジトリにコミットしたくない
  • 開発者によって API サーバの URL (ホスト名だったりポート番号だったりステージングが複数あったり) が違うなどのケースがあるのでそれぞれの環境に合わせて設定できるようにしたい
  • それぞれの API に接続する開発版(Debug)、ステージング版(Staging)、リリース版(Release)の3つのビルドを別のアプリとして同時に端末にインストールしたい
    • リリース版のビルドをインストールしたらステージング版のビルドが上書きされたりすると困る

Web の開発でしたら .env を使って環境変数としてこれらを管理するのですが、iOS 開発においてもなるべく Xcode 上の GUI でポチポチ設定せずにできるだけテキストベースで管理できる方法を考えました。

環境
Xcode Version 13.2.1 (13C100)

下準備

Staging Configuration を作成する

まずは Project の設定を確認します。
Project > Info の中の Configurations を見ると、デフォルトでは Debug, Release の2種類のビルド設定が用意されています。

Staging ビルド用の設定も追加したいので、Release を複製して Staging を作ります。

(Debug を複製して作っても良いのですが、サーバ開発だとステージング環境はなるべく本番環境と近づけておくことが大事と考えており、アプリ開発においても同様に考え Release を複製しました。)

ビルド設定ファイル (.xcconfig) の準備

次に Debug, Staging, Release の3環境分のビルド設定をテキストベースで管理するために、
Xcode 上で .xcconfig ファイルを3つ (Debug.xcconfig, Staging.xcconfig, Release.xcconfig) 用意します。

3つ作り終えるとこんな感じになります。(Config グループにまとめています)

作成した .xcconfig ファイルを Project > Info から各 Configurations に割り当てます。

Scheme を準備する

Xcode 上部の Scheme から Configuration を切り替えて実行できるようにしておくと便利なのでやっておきます。

デフォルトだと Xcode プロジェクト作成時に指定した Project Name と同じ名前の Scheme が一つ用意されているだけです。スクリーンショットだと EnvSample がそれにあたります。

デフォルトの Scheme を複製して Debug, Staging, Release の3つの Scheme を作成します。

複製したのち、Build Configuration を各環境に合わせたものに設定するようにしてください。

また、Shared にチェックを入れておくとプロジェクトを clone したチームメンバーもその Scheme を使えるようになるので入れておくと良いと思います。

これで下準備は完了です。
デフォルトで用意されている Scheme (ここでは EnvSample) は削除してしまって良いと思います。

環境ごとに違うアプリとしてインストールできるようにする

Debug, Staging, Release の各ビルドを端末に違うアプリとしてインストールできるようにします。
具体的には Bundle Identifier を分けることで別々のアプリとしてインストールできるようにし、かつわかりやすいようにアプリ名 (Bundle Display Name) も分けて設定できるようにしたいと思います。

Target にデフォルトでハードコーディングされている値を消す

デフォルトでは Target の設定として Xcode プロジェクトの作成時に指定した値がハードコーディングされています。
これを .xcconfig で変更できるようにするため、一回そのハードコーディングされている値を削除します。

Target の Build Settings を確認してください。Product Bundle Identifier がそれにあたります。
単純に項目を選択して Delete キーを押してください。

削除し終えると値が空になると思います。

Bundle Display Name に関しては元々空なので何もしなくて大丈夫です。

xcconfig に値を書き込む

Debug.xcconfig
INFOPLIST_KEY_CFBundleDisplayName = [DEBUG]$(TARGET_NAME)
PRODUCT_BUNDLE_IDENTIFIER = dev.ebisawa.EnvSample-debug
Staging.xcconfig
INFOPLIST_KEY_CFBundleDisplayName = [STG]$(TARGET_NAME)
PRODUCT_BUNDLE_IDENTIFIER = dev.ebisawa.EnvSample-staging
Release.xcconfig
INFOPLIST_KEY_CFBundleDisplayName = 環境変数サンプル
PRODUCT_BUNDLE_IDENTIFIER = dev.ebisawa.EnvSample

ちなみにキー名は Xcode の Quick Help の Declaration を見るとわかります。

これで完了です。

リポジトリにコミットしたくない値を別の xcconfig ファイルで管理する

リポジトリにコミットして良い、かつ各メンバーで同じ値を使ってしまって良いのであれば先ほど作成した各 .xcconfig ファイルに直接書き込んでしまって大丈夫です。

しかし初めに述べた要件のように、リポジトリにコミットしたくなかったり、開発者ごとに値が違ったりする場合のために別の .xcconfig を作成し、{Debug,Staging,Release}.xcconfig から読み込むような形にしようと思います。

Env-*.xcconfig ファイルを作成する

Env-Debug.xcconfig, Env-Staging.xcconfig, Env-Release.xcconfig ファイルを作成します。(Staging, Release は必要になった時でも大丈夫です。)
内容は以下のような感じです。

Env-Debug.xcconfig
API_SCHEME = http
API_BASE_URL = localhost:3000
API_KEY = XXXXXXX

(本当は API_BASE_URL = http://localhost:3000 と書きたいのですが、.xcconfig の仕様上 // をエスケープできないために別変数に分けています)

そしてこの Env-Debug.xcconfig を Debug.xcconfig から読み込むように、以下を追記します。

Debug.xcconfig
#include "Env-Debug"

必要に応じてStaging, Release についても同様に設定してください。

これにより、Env-Debug.xcconfig に書かれた内容もあたかも Debug.xcconfig に書かれている内容であるかのように参照できるようになります。

なお、もし Env-Debug.xcconfig ファイルが存在しない場合には Xcode が warning を出します。#include の代わりに #include? を使えば warning が出ないので、ワークフローに合わせて適宜使うと良いと思います。

Env-*.xcconfig は当然リポジトリにチェックインしたくないので .gitignore で制御してください。

独自のビルド設定をコードから参照できるようにする

Target の設定の Custom iOS Target Properties に以下のように追加することで、ソースコードからアクセスできるようになります。

let apiBaseURL = Bundle.main.value(forKey: "API_BASE_URL") as! String

参考

https://help.apple.com/xcode/mac/11.4/#/dev745c5c974
https://nshipster.com/xcconfig/
https://nshipster.com/secrets/

Discussion