【Unity】iOSネイティブプラグイン開発を完全に理解する - Xcodeの設定を自動化する


本編「[【Unity】iOSネイティブプラグイン開発を完全に理解する](https://qiita.com/mao_/items/5b33c90e533a538570b8)」の付録記事です。
[記事中での用語や略称](https://qiita.com/mao_/items/5b33c90e533a538570b8#%E8%A8%98%E4%BA%8B%E4%B8%AD%E3%81%A7%E8%A7%A3%E8%AA%AC%E3%81%99%E3%82%8B%E8%A8%80%E8%AA%9E%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)についてはそのまま本編に倣う形で記載していきます。

--------------------------------------------

Unityのビルド結果にあるプロジェクトファイル(`.xcodeproj`)を開き、Xcode上でターゲット[^1]を選択してから`Build Settings`を開けば名前の通りに**アプリケーションのビルドに関する設定**を行えます。

例えば以下のスクリーンショットでは例として「Swiftのバージョン指定」を選択してます。

[^1]: `ターゲット`については後述します

こちらの設定はXcode上から手動で変更する以外にも、**Unity上からビルド毎に自動で設定を変更することも可能であり、今回はそのやり方について解説していきます。**

![スクリーンショット 2021-04-08 5.48.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/ce8d09b9-611d-2ca8-48ec-4dc738264351.png)





# ※ 前提としてUnityの場合には手動で変える運用はオススメできない

設定項目の変更状態はXcodeのプロジェクトファイル(`.xcodeproj`)に紐付かれることになりますが、Unityと言った**プロジェクトファイルを自動生成する様な環境だと、<font color="Red">例えば`Replace`相応のビルドを行った際にはビルド設定が適用された`.xcodeproj`も消えてしまうため、</font>都度手動で再設定を行う必要が出てきます。**

手動設定は運用を踏まえると、ビルドの自動化との相性問題もあれば、そもそもヒューマンエラーに繋がる懸念も出てきます。
※逆にビルドエラー解決などの為に「一瞬手動で変えてみて試す」と言ったのは全然有りだと思います。ただ、最終的にはUnity上から自動で設定を適用するようにした方が良いかもです。





# `[PostProcessBuild]`を利用して設定を自動化

Unityからプロジェクトファイルにある設定を書き換えるには、[PostProcessBuildAttribute](https://docs.unity3d.com/ScriptReference/Callbacks.PostProcessBuildAttribute.html)と言う属性を利用したEditor拡張を実装します。

こちらを利用すると**ビルドが完了したタイミングで属性を付けたメソッドを呼び出すことが出来るようになるため、**ビルド完了後にXcodeプロジェクトファイルにアクセスして内容を書き換えると言ったアプローチを取ることが出来るようになります。


```csharp:XcodePostProcess.cs
#if UNITY_IOS
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

namespace MinimumExample.Editor
{
    static class XcodePostProcess
    {
        /// <summary>
        /// ビルド完了後に呼び出される
        /// </summary>
        /// <param name="target">ビルドターゲット</param>
        /// <param name="path">ビルド結果のパス</param>
        [PostProcessBuild]
        static void OnPostProcessBuild(BuildTarget target, string path)
        {
            // ビルドターゲットがiOSなら処理
            if (target != BuildTarget.iOS) return;

            // TODO: ここでビルド結果に対して手を加えることが出来る
        }
    }
}
#endif

```


# `PBXProject`を利用してXcodeのプロジェクトファイルを書き換える

Unityからプロジェクトファイルの内容を書き換えるには[PBXProject](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.html)と言うiOS(と言うよりはXcode)向けのAPIを利用します。

簡単な例として「Swiftバージョンを5.0に設定する」例を取り上げつつ、以下の要点について順に解説していきます。

- A. ビルド結果のパスから`PBXProject`を読み込んで、終わったら変更結果で上書き
- B. 設定を適用するターゲットのGUIDを取得
- C. 設定変更の適用

```csharp:XcodePostProcess.cs
        /// <summary>
        /// Swiftを実装するにあたって必要な設定を自動で適用する
        /// </summary>
        /// <param name="target">ビルドターゲット</param>
        /// <param name="path">ビルド結果のパス</param>
        [PostProcessBuild]
        static void OnPostProcessBuild(BuildTarget target, string path)
        {
            if (target != BuildTarget.iOS) return;

            // A. ビルド結果のパスからPBXProjectを読み込み
            var projectPath = PBXProject.GetPBXProjectPath(path);
            var project = new PBXProject();
            project.ReadFromString(File.ReadAllText(projectPath));

　　　　　　  // B. 設定を適用するターゲットのGUIDを取得
            // 2019.3からは`UnityFramework`に分離しているので、targetGuidはこちらを指定する必要がある。
            var targetGuid = project.GetUnityFrameworkTargetGuid();

            // C. 設定変更の適用 (Swiftバージョンの設定)
            project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");

            // A. 変更結果で上書き
            File.WriteAllText(projectPath, project.WriteToString());
        }
```

## 【補足】 そもそも`PBXProject`とは？

`PBXProject`はXcodeプロジェクトファイルの中に含まれる設定ファイル的なものであり、主にプロジェクト内にあるファイルの定義/フォルダ階層構造/ビルド設定と言った要素を持ちます。

ビルド結果にある`.xcodeproj`を選択したら「右クリック -> パッケージの内容を表示」を選択することで、Finder上からアクセスすることが出来ます。(以下にある`project.pbxproj`が該当)

```
[XXX.xcodeproj]
    L project.pbxproj
    L project.wxworkspace
    L [xcshareddata]
```

**ちなみに`project.pbxproj`自体はテキスト形式なのでテキストエディタで開いて編集することも可能です。**





## ■ A. ビルド結果のパスから`PBXProject`を読み込んで、終わったら変更結果で上書き

`PostProcessBuild`から呼び出されるメソッドはパラメータとして「ビルド結果のパス」を受け取ることが出来るので、こちらを[PBXProject.GetPBXProjectPath](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.GetPBXProjectPath.html)に渡すことで**実際の`PBXProject`のパスを取得します。**

あとは取得したパスから`PBXProject`を文字列として読み込み、それを[ReadFromString](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.ReadFromString.html)に渡すことで初期化を完了させます。

```csharp:XcodePostProcess.cs

        /// <param name="target">ビルドターゲット</param>
        /// <param name="path">ビルド結果のパス</param>
        [PostProcessBuild]
        static void OnPostProcessBuild(BuildTarget target, string path)
        {
            if (target != BuildTarget.iOS) return;

            // A. ビルド結果のパスからPBXProjectを読み込み
            var projectPath = PBXProject.GetPBXProjectPath(path);
            var project = new PBXProject();
            project.ReadFromString(File.ReadAllText(projectPath));
```

そして後述する諸々の設定の適用が完了したら、上記で取得した`PBXProject`のパスに対して結果を書き込みます。
(先行して書いてますが、これ自体は一番最後のプロセスになります)

```csharp:XcodePostProcess.cs
            // A. 変更結果で上書き
            File.WriteAllText(projectPath, project.WriteToString());
```

※この例では`ReadFromString`と`WriteToString`で読み書きしましたが、他にも関連するAPIとしては以下のようなものが存在します。

- [PBXProject.ReadFromStream](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.ReadFromStream.html)
    - [PBXProject.WriteToStream](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.WriteToStream.html)
- [PBXProject.ReadFromFile](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.ReadFromFile.html)
    - [PBXProject.WriteToFile](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.WriteToFile.html)





## ■ B. 設定を適用するターゲットのGUIDを取得

Unity2019.3以降からは[PBXProject.GetUnityFrameworkTargetGuid](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.GetUnityFrameworkTargetGuid.html)を実行して**`UnityFramework`と言うターゲットのGUID**を持っておきます。
GUIDの用途については後述します。


```csharp:XcodePostProcess.cs
            // B. 設定を適用するターゲットのGUIDを取得
            // 2019.3からは`UnityFramework`に分離しているので、targetGuidはこちらを指定する必要がある。
            var targetGuid = project.GetUnityFrameworkTargetGuid();
```

### 【補足】 そもそも"ターゲット"とは？

ターゲットとはXcodeプロジェクトにある「ビルド設定のまとまり」みたいなものであり、それぞれが独自の設定項目(アプリ名/ビルド設定/Bundle Identifier)などを持ちます。

Unityの場合には予め必要なものが設定されており、Xcodeの画面で言うと以下の①を選択したら表示される②の項目がターゲットになります。

![スクリーンショット 2021-04-10 23.57.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/221461ed-a5ee-7cc1-5320-a9528860e898.png)

2019.3からはアプリ本体側の機能が`UnityFramework`に集約されているので、**[PBXProject.GetUnityFrameworkTargetGuid](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.GetUnityFrameworkTargetGuid.html)からこの`UnityFramework`のGUIDを取得する必要があります。**

もっと具体的に「それぞれのターゲットに何がどう含まれているのか？」については以下の公式ドキュメントの`Project Targets`を御覧ください。

- [Structure of a Unity Xcode Project](https://docs.unity3d.com/2019.4/Documentation/Manual/StructureOfXcodeProject.html)


### 【補足】 Unity2019.3前後ではプラグインに関する設定が変わってくるので注意

`Unity 2019.3`から新機能として**「[Unity as a Library](https://blogs.unity3d.com/jp/2019/06/17/add-features-powered-by-unity-to-native-mobile-apps/)」**が入った影響で、iOS向けビルド後のXcodeプロジェクトファイル(`.xcodeproj`)の構成に大きく変更が掛かりました。

この記事中では2019.4をターゲットとしているので、基本的には2019.3以降の書き方で解説をしてますが、**2018系統などを利用する場合にはこれらの設定が変わってくることについては念頭に置いておく必要があります。**

詳細は以下の記事にて纏めているので、こちらを御覧ください。

- [【Unity】iOSネイティブプラグインをSwiftで実装する際には、2019.3前後で設定方法が変わる](https://qiita.com/mao_/items/2305822054dadb2c5da8)





## ■ C. 設定変更の適用

残るは設定変更の適用です。
今回の題材である「Swiftバージョンを5.0に設定する」の場合には、[PBXProject.SetBuildProperty](https://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.SetBuildProperty.html)を利用します。

- 第一引数には`ターゲットのGUID`を指定
    - → **`B.`の項目で取得した`UnityFramework`のGUIDを指定します**
- 第二引数には`ビルドプロパティの名前`を指定
- 第三引数には`プロパティに設定したい値`を指定
    - → 次で詳細に触れます

```csharp:XcodePostProcess.cs
            // C. 設定変更の適用 (Swiftバージョンの設定)
            project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");
```



### ビルドプロパティの名前について

ここで指定するプロパティ名はXcode上から確認することが出来ます。

先ずはTARGETSから`UnityFramework`を選択し、[Build Settings]を開いたら設定したい項目を選択します。
(今回で言うと`Swift Language Version`)

選択したら以下のスクリーンショットの右上辺りにある①を選択して`Quick Help`を表示し、②に記載されている文字列をUnity側で指定します。
→ この例で言うと`SWUFT_VERSION`が該当する文字列

![スクリーンショット 2021-04-11 1.14.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/57ca500c-e79a-0561-9c19-ade03694b12d.png)

他にも[Xcode Build Settings](https://xcodebuildsettings.com/#category-core-build-system)と言うサイトには一通りのビルドプロパティや初期値、中には簡単な解説が記載されているので覚えておくと便利です。

- [Xcode Build Settings](https://xcodebuildsettings.com/#category-core-build-system)



### プロパティに設定したい値について

引数に渡す値は文字列である必要があるために`"5.0"`を渡してます。

少し余談にはなりますが、もしValueに何を渡せばよいのか分からない場合には、**プロジェクトファイルに含まれる`project.pbxproj`をテキストエディタで開き、該当するビルドプロパティで検索してやることで設定値を割り出せたりします。**

例えば`"SWIFT_VERSION"`で検索した場合には↓の様に`5.0`と言った値が設定されているので、Unity側で渡すパラメータもこちらに準拠させればOKです。

```text:project.pbxproj
buildSettings = {
    (省略)
    SWIFT_VERSION = 5.0;
    (省略)
};
```





# その他設定項目

Swiftのバージョン指定を例にビルド設定の変更方法について解説してきましたが、他にも以下のような項目も設定可能です。

- `Framework`の追加
- `Info.plist`の設定

これらの設定例については以下のサイトが参考になります。

- [UnityでXcodeの設定を自動化する方法まとめ](https://smartgames.hatenablog.com/entry/2016/06/19/164052)





# <font color="Orange">設定の競合に注意</font>

`[PostProcessBuild]`を利用した設定の自動化ですが、**こちらの処理自体はプロジェクト中に複数実装することが出来ます。**

もし複数箇所にて`PBXProject`の書き換えを行った場合には、随時呼び出し順に従って設定が書き換えられていく事になりますが...<font color="Red">注意しないと**同一設定の書き換え**が発生して競合するなんてことが起こり得ます。</font>

## 同一設定は後から書き換えられたほうが優先される

例えば以下の`XcodePostProcess1.cs`と`XcodePostProcess2.cs`と言うソースがあったとして、先に呼び出される想定の`XcodePostProcess1.cs`では**「`SWIFT_VERSION`を`5.0`」**に書き換えるとします。

そして次に呼び出される想定の`XcodePostProcess2.cs`にて**「`SWIFT_VERSION`を`4.0`」**に書き換えたとしたら、<font color="Red">**最終的なビルド結果は「SWIFT_VERSION : 4.0」が適用されることになります。**</font>


<details><summary>**`XcodePostProcess1.cs` (クリックで展開)**</summary><div>

```csharp:XcodePostProcess1.cs
        // NOTE: 属性の引数に呼び出し順を指定可能
        [PostProcessBuild(1)]
        static void OnPostProcessBuild(BuildTarget target, string path)
        {
            if (target != BuildTarget.iOS) return;

            var projectPath = PBXProject.GetPBXProjectPath(path);
            var project = new PBXProject();
            project.ReadFromString(File.ReadAllText(projectPath));
            var targetGuid = project.GetUnityFrameworkTargetGuid();

            // SWIFT_VERSIONを`5.0`に書き換え
            project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");
            File.WriteAllText(projectPath, project.WriteToString());
        }
```

</div></details>

<details><summary>**`XcodePostProcess２.cs` (クリックで展開)**</summary><div>

```csharp:XcodePostProcess2.cs
        [PostProcessBuild(2)]
        static void OnPostProcessBuild(BuildTarget target, string path)
        {
            if (target != BuildTarget.iOS) return;

            var projectPath = PBXProject.GetPBXProjectPath(path);
            var project = new PBXProject();
            project.ReadFromString(File.ReadAllText(projectPath));
            var targetGuid = project.GetUnityFrameworkTargetGuid();

            // SWIFT_VERSIONを`4.0`に書き換え
            project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "4.0");
            File.WriteAllText(projectPath, project.WriteToString());
        }
```

</div></details>


## 汎用的なプラグインを実装する際には、書き換えに注意すること

もし色んなプロジェクトで使える想定の汎用的なプラグインを実装するとしたら、上記のような性質があるので**`PBXProject`の書き換えには注意する必要があります。**
(「導入したプラグイン側で意図せずに`PBXProject`を書き換えてしまった」、若しくは「導入先のプロジェクトで期待する設定が書き換えられてしまった」なんてことが起こり得るので)

**もしプラグインの都合で`PBXProject`を書き換える必要性が出てきた場合には、README辺りに「`PBXProject`のこの設定を書き換えます」 or 「導入するなら`PBXProject`のこの設定を書き換えてください」と明記するなりした方が良いかもしれません。**