simctl コマンドで iOS シミュレータの初期セットアップを実行する

# はじめに

仕事やプライベートの開発で iOS シミュレータを利用することが多いのですが、Xcode をアップデートするたびにシミュレータが初期化されてしまうのが地味にストレスでした。少し調べてみたところ `simctl` を利用することでサクっとシミュレータの初期セットアップが実行できることがわかったので書き留めておきたいと思います。

# 環境

Xcode 11.5

# できたスクリプト

シミュレータを起動した状態で下記スクリプトを実行すると、

- Charles Proxy の CA 証明書のインストール
- 使用言語/地域の変更（日本語/日本）

が完了します。

```bash:setup-simulator.sh
#!/usr/bin/env bash                                                                                                                                  

set -eu

device=${1:-booted}
echo "Target Device: $device"

# Charles Proxy の CA 証明書をインストールする                                                                                                     
cert="${HOME}/Library/Application Support/Charles/ca/charles-proxy-ssl-proxying-certificate.cer"
if [ -e "$cert" ]; then
  xcrun simctl keychain $device add-root-cert "$cert"
fi

# 使用言語を日本語にする                                                                                                                           
xcrun simctl spawn $device defaults write -globalDomain AppleLanguages -array ja-JP en-JP

# 地域を日本にする                                                                                                                                 
xcrun simctl spawn $device defaults write -globalDomain AppleLocale -string ja_JP

# シミュレータの SpringBoard を再起動する                                                                                                          
killall -HUP SpringBoard
```

以下、簡単な解説です。

## ルート証明書のインストール

[Xcode 11.4](https://developer.apple.com/documentation/xcode_release_notes/xcode_11_4_release_notes) で `keychain` サブコマンドが追加されました。

```
$ xcrun simctl keychain <device> add-root-cert my-selfsigned.cer
```

仕事では [Charles](https://www.charlesproxy.com/) をよく利用するのですが、そのルート証明書を `keychain` サブコマンドで一発でインストールできます。設定アプリの [一般] > [情報] > [証明書信頼設定] まで進んで証明書のスイッチをオンにする必要もありません。

## 設定の変更

`spawn` サブコマンドではシミュレータ上で任意のプロセスを実行できます。

```
$ xcrun simctl spawn --help
Spawn a process by executing a given executable on a device.
Usage: simctl spawn [-w | --wait-for-debugger] [-s | --standalone] [-a <arch> | --arch=<arch>] <device> <path to executable> [<argv 1> <argv 2> ... <argv n>]

The path to the executable is searched using the following rules:
   <path> contains no / characters: search the device's $PATH. This is similar to how most shells work, but searches the device's path instead of the host's path.
   <path> starts with /: Assume a literal path to the binary. This must start from the host's root.
   <path> contains non-leading / characters: search relative to the current directory first, then relative to the device's $SIMULATOR_ROOT.

If you want to set environment variables in the resulting environment, set them in the calling environment with a SIMCTL_CHILD_ prefix.
```

macOS と同様に `defaults` コマンドを利用することで、設定の参照や変更が可能です。
たとえば日本語設定済みのシミュレータの設定値を参照すると下記のような結果が得られます。

```bash
$ xcrun simctl spawn booted defaults read -globalDomain
{
    AKLastIDMSEnvironment = 0;
    AddingEmojiKeybordHandled = 1;
    AppleITunesStoreItemKinds =     (
        "itunes-u",
        movie,
        album,
        ringtone,
        "software-update",
        booklet,
        tone,
        "music-video",
        song,
        podcast,
        software,
        audiobook,
        "podcast-episode",
        wemix,
        eBook,
        mix,
        artist,
        document
    );
    AppleKeyboards =     (
        "ja_JP-Kana@sw=Kana;hw=US",
        "en_US@sw=QWERTY;hw=Automatic",
        "emoji@sw=Emoji",
        "en_US@sw=QWERTY;hw=Automatic"
    );
    AppleKeyboardsExpanded = 1;
    AppleLanguages =     (
        "ja-JP",
        "en-JP"
    );
    AppleLanguagesSchemaVersion = 1001;
    AppleLocale = "ja_JP";
    ApplePasscodeKeyboards =     (
        "en_US@sw=QWERTY;hw=Automatic",
        "emoji@sw=Emoji",
        "en_US@sw=QWERTY;hw=Automatic"
    );
    CarCapabilities =     {
        CarCapabilitiesDefaultIdentifier =         {
            CRCapabilitiesDisabledFeatureKey = 0;
            CRCapabilitiesUserInterfaceStyleKey = 2;
            CRCapabilitiesVersionKey = "";
            CapabilitiesDashboardRoundedCornersKey = "{{13, 0}, {0, 0}}";
            CapabilitiesNowPlayingAlbumArtKey = 2;
            CapabilitiesViewAreaInsetKey = "{{0, 0}, {0, 0}}";
        };
    };
    PKKeychainVersionKey = 7;
}
```

ドキュメントは探していませんが AppleLanguages と AppleLocale が言語と地域の設定と推測されますので、まっさらなシミュレータに対して下記コマンドで値を設定します。

```bash
$ xcrun simctl spawn booted defaults write -globalDomain AppleLanguages -array ja-JP en-JP
$ xcrun simctl spawn booted defaults write -globalDomain AppleLocale -string ja_JP
```

設定を反映するためにシミュレータの再起動が必要になりますが、simctl の `shutdown`, `boot` サブコマンドでは時間が掛かるので、SpringBoard プロセスを再起動するのが速くてよいです（ただし起動中の全シミュレータが再起動します）。

```bash
$ killall -HUP SpringBoard
```

# おわりに

ひとつひとつの設定は慣れるとすぐできるようになりますが、Xcode アップデートのたびに利用するシミュレータの数だけ設定しているとチリツモなので、ちょっとした改善を積み重ねていきたいです。

# 参考

- https://www.iosdev.recipes/simctl/
- https://gist.github.com/koke/e3106e4531e40d2ba423b76ad789caff