Xcodeで設定を分ける場合はxcconfigを使うということも知っておいてほしい

## はじめに

XcodeでiOSアプリ開発をする場合に、設定を切り替えたい場合はxcconfigという手段があるということをグダグダ書いておきます。

グダグダ書く理由は２つ

- ConfigurationはDebug/Releaseの２つでxcconfig分ければいいだけ
- SchemeでたかだかWeb APIの接続先が違うくらいの小さな設定を変えてしまうのはオーバーキル

極論を書くと「通信先を切り替えたいだけでSchemeを切り替えるのはGUIから設定を書き換えられても気が付かないリスクを孕んでいるのでオーバーキルしている」ということです。

記事を書くモチベーションは

- xcconfigに関する良い記事は多いんですが、なぜかそこまで浸透してるとは思えず、なぜか人はオーバーキルしてしまう
- 自分で設定してないプロジェクトでSchemeごとに設定を変えられると、差分がないということだけでも証明することが面倒

という感じで、いきあたりばったりでxcconfigについて書いてみます。間違いがあるかもしれません。お手数おかけしますがコメントにお願いします。

## 基礎

Xcodeでは、プロジェクトのビルド設定をプレーンテキストファイルで指定し、Configuration（DebugビルドやReleseビルド）ごとに異なる設定を指定できます。

https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project?changes=_8

![build-configuration-mapping~dark@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16400/0b6aaaf3-10f0-2c3f-abe7-c3a334a78c3f.png)

以下、具体的にグダグダ書いておきます。

### Configurationごとに読み込める

まず、Projectの下にターゲットがあり、それは複数のConfiguration（基本はDebugとReleaseのみ）が存在し、xcconfigがターゲットのConfigurationごとに利用できるというイメージです。

例を示します

```mermaid
classDiagram

Project "1" -- "*" Target
Target "1" --> "1" `Debug(Target Configuration)` 
Target "1" --> "1" `Release(Target Configuration)` 
`Debug(Target Configuration)` "1" --> `debug.target.xcconfig`
`Release(Target Configuration)` "1" --> `production.target.xcconfig`
```

この時点で、info.plistのパスも切り替えられるのでDebugとReleaseでinfo.plistが変更可能な設定は切り替えられます（がややこしくなるためそれはここでは説明しません）

### xcconfigを別に用意する

実務的には2つの接続先だけでは足らず、「ステージング環境も試したいんじゃよ」となるでしょう。もちろんそういう場合xcconfigに書けばいいだけにするのがミスがないはずです。だったら`stg.target.xcconfig`を用意し、`debug.target.xcconfig`を置き換えたらいいだけです。

例を示します

```mermaid
classDiagram

Project "1" -- "*" Target
Target "1" --> "1" `Debug(Target Configuration)` 
Target "1" --> "1" `Release(Target Configuration)` 
`Debug(Target Configuration)` "1" --> `stg.target.xcconfig`
`Release(Target Configuration)` "1" --> `production.target.xcconfig`

class `debug.target.xcconfig`
```

大抵はDebugとRelease以外を作る理由はないと思います。なぜなら大抵は「Web APIのホストを変更したい」と結局は文字列を置き換えるだけでしょう。Debugビルドでステージング環境用のxcconfigを読み込めればいいだけです。

`stg.target.xcconfig`など作らず、`debug.target.xcconfig`はテキストファイなので内部のテキストを置き換えてもいいでしょう。

### xcconfigの共通化

複数のxcconfigを用意すると、「stgとかdebugとかで共通のやつどうすんねん。DRYって知っとんのか」と思うかもしれません。.xcconfigは別の.xcconfigを読み込むことができますので、共通の設定はそちらに書くことが可能です。

例を示します

```mermaid
classDiagram

Project "1" -- "*" Target
Target "1" --> "1" `Debug(Target Configuration)` 
Target "1" --> "1" `Release(Target Configuration)` 
`Debug(Target Configuration)` "1" --> `debug.target.xcconfig`
`Release(Target Configuration)` "1" --> `production.target.xcconfig`

`debug.target.xcconfig` --|> `target.root.xcconfig`: include
`production.target.xcconfig` --|> `target.root.xcconfig`: include
`stg.target.xcconfig` --|> `target.root.xcconfig`: include
```

## Projectにもxcconfigを設定できる

公式のリファレンスにもありますが、もう少し基礎について書いておくと、Projectにもそれぞれのxcconfigを用意することができます。

```mermaid
classDiagram

Project "1" -- "*" Target
Project --> Configuration
Configuration --> `Debug(Project Configuration)`
Configuration --> `Release(Project Configuration)`

`Debug(Project Configuration)` --> `debug.project.xcconfig`
`Release(Project Configuration)` --> `release.project.xcconfig`

Target "1" --> "1" `Debug(Target Configuration)` 
Target "1" --> "1" `Release(Target Configuration)` 
`Debug(Target Configuration)` "1" --> `debug.target.xcconfig`
`Release(Target Configuration)` "1" --> `production.target.xcconfig`

`debug.target.xcconfig` --|> `target.root.xcconfig`: include
`production.target.xcconfig` --|> `target.root.xcconfig`: include
`stg.target.xcconfig` --|> `target.root.xcconfig`: include
```

正直オーバーキルしそうな感じなので、もし必要があったら分けたらいいとは思います。上の例では`debug.project.xcconfig`と`release.project.xcconfig`としましたが、公式リファレンスだとDebugもReleaseも`BaseSettings.xcconfig`を読み込んでいます。

まあ基礎を書いておくとそこから何か問題解決の手がかりがつかめるかもしれないので書き残しときます。


## CocoaPodsのxcconfigを読み込む

もともとCocoaPodsはxcconfigファイルをConfigurationごとに自動生成し開発者へそれを使わせます。CocoaPodsが開発者へWorkspaceを利用させる意図はそれです。

つまり、自前でxcconfigを作るとCocoaPodsの生成したxcconfigと衝突します。そのため、自前のxccofigでCocoaPodsのxcconfigをincludeすることになります。

```mermaid
classDiagram

Project "1" -- "*" Target
Target "1" --> "1" `Debug(Target Configuration)` 
Target "1" --> "1" `Release(Target Configuration)` 
`Debug(Target Configuration)` "1" --> `debug.target.xcconfig`
`Release(Target Configuration)` "1" --> `production.target.xcconfig`

`debug.target.xcconfig` --|> `target.root.xcconfig`: include
`production.target.xcconfig` --|> `target.root.xcconfig`: include
`stg.target.xcconfig` --|> `target.root.xcconfig`: include

`debug.target.xcconfig` --|> `Pods-YourTargetName.debug.xccconfig`: include
`stg.target.xcconfig` --|> `Pods-YourTargetName.debug.xccconfig`: include
`production.target.xcconfig` --|> `Pods-YourTargetName.release.xccconfig`: include
```

## xcconfigの課題

### GUIの設定を優先される

万能すぎに見えるxcconfigですが、Xcode上のGUIから同じ項目を変更した場合はそのGUIの設定を優先します。これは差分が見えづらいのでPRに紛れ込んだときに見のがしちゃう可能性があって知らないうちに設定ファイルとは違う挙動になるきっかけになります。

この課題を解決するとしたらCIでproject設定が.xcconfig以外から変えられてるぞって警告するとかブロックするとかでしょうかね。やってる人いたら教えて下さい。

## おわりに

### ConfigurationはDebug/Releaseの２つでxcconfig分ければいいだけ

私が想定外のことがあるのかもしれません。もしinfo.plistやxcconfigで設定が分けられないことがあればコメントで教えていただければと思います。

## SchemeでたかだかWeb APIの接続先が違うくらいの小さな設定を変えてしまうのはオーバーキル

Schemeが変更できる階層は広く、これを分けてしまうとそれぞれのSchemeごとの差分はそれぞれの階層ごとの差分となり、私には差分を比較しづらいです。

- Scheme
    - Project
        - Target
            - Configuration
                - xcconfig
                - Build options

たとえばWeb APIの接続先を変えたいだけなのに、Schemeでそれを変更するならその接続先AとBのDebugビルドの設定は別々になっててもいいの？とかそれに気がつけるの？っていう危惧をしています。

もちろん、XcodeGenなどを利用することでプロジェクト設定をコード化できるので、その場合はSchemeで分けることにはあんまり抵抗はないです。