DebugとReleaseで定義を切り替える3つ目のベストな方法


ほとんどのアプリは以下の方法で分けていると思います。
一つは、User-Definedを使う方法。
一つは、プリプロセッサマクロを使う方法。

これらよりも、自分がベストだと思う方法があるよ。という記事です。

MavenのprofileやGradleのAndroid Pluginのように環境別のファイルを用意して、ビルドにどっちを使うかを判断させる方法です。

# やり方

まずは、```Environment-Info-develop.plist```、 ```Environment-Info-production.plist``` のように環境ごとのファイルを作成します。
中身は以下のように環境ごとの違いを記述します。

```Configuration/Environment-Info-develop.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>apiUrl</key>
	<string>https://develop.hoge.jp</string>
</dict>
</plist>
```

```Configuration/Environment-Info-production.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>apiUrl</key>
	<string>https://hoge.jp</string>
</dict>
</plist>
```

次に、Build Phasesで、Run Scriptを作成します。
内容は、以下です。つまり、選択したBuild Configurationに対応するファイルを```Environment-Info.plist``` としてアプリにバンドルさせています。


```sh:/bin/sh
BUILD_PRODUCT="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app"
PROJECT_PATH="${PROJECT_DIR}/${PROJECT_NAME}"
INFO_BUILD_PATH="${BUILD_PRODUCT}/Environment-Info.plist"

rm -rf "${INFO_BUILD_PATH}"
if [ "${CONFIGURATION}" = "Debug" ]; then
  cp "${PROJECT_PATH}/Configuration/Environment-Info-develop.plist" "${INFO_BUILD_PATH}"
  echo "Development Environment-Info copied into ${BUILD_PRODUCT}"
else
  cp "${PROJECT_PATH}/Configuration/Environment-Info-production.plist" "${INFO_BUILD_PATH}"
  echo "Production Environment-Info copied into ${BUILD_PRODUCT}"
fi
```

最後にこの内容を扱うクラスを作成して利用するだけです。
取得できなかった場合は単純なバグなので、すぐに落として気づけるように、Forced unwrappingさせます。

```EnvironmentHolder.swift
public class EnvironmentHolder {

    static let sharedInstance = EnvironmentHolder()

    let apiUrl: String

    private init() {
        let bundle = Bundle.main

        let environmentPath = bundle.path(forResource: "Environment-Info", ofType: "plist")!
        let environmentDict = NSDictionary(contentsOfFile: environmentPath)!
        self.apiUrl = environmentDict.object(forKey: "apiUrl") as! String
    }
}
```

# User-Definedとの比較

User-Definedは、Target > Build Settings で「+」を押下して 「add User-Defined Setting」 でConfigurationごとに設定していく方法です。

![スクリーンショット 2017-05-09 13.46.44.png](https://qiita-image-store.s3.amazonaws.com/0/10494/3e85ac37-8390-30c8-7ec2-e02954e48d8b.png "スクリーンショット 2017-05-09 13.46.44.png")


そして、Info.plistに書いて

```Info.plist
       <key>apiUrl</key>
       <string>${apiUrl}</string>
```

コードで以下のように呼び出します。

```swift
let apiUrl = Bundle.main.object(forInfoDictionaryKey: "apiUrl")
```


あまり、良くないところは、User-Definedが、```*.xcodeproj/project.pbxproj``` に保存されることです。
開発中は、Debugの場合だけ書いておいて、後でコピペして本番用のを用意するということができません。

一方、最初に提案した方法は、ただのplistなので、簡単にコピペができますし、diffに気づきやすいです。

# プリプロセッサマクロとの比較

Objective-cなら Preprocessor Macrosに、例えば、```DEBUG=1``` と書き、
Swiftなら Other Swift Flags に ```-D DEBUG``` と書き、

コードで以下のように呼び出します。

Objective-Cの場合

```objc
 #ifdef DEBUG
    // ...
 #elif RELEASE
    // ...
 #endif
``` 
 

Swiftの場合

```swift
  #if DEBUG
    // ...
  #elseif RELEASE
    // ...
  #endif
```

あまり良くないところは、本質的ではない処理がコード上に現れることです。値が少なければ良いですが、多くなると可読性が損なわれ、バグに気付きにくくなります。
この方法では、EnvironmentHolder.swiftのようなクラスを用意して、一つのクラスだけに閉じる戦略がいいでしょう。
いずれにしろ、最初に提案した方法は余計な分岐がないことがメリットになります。