【Unity】iOSネイティブプラグイン開発を完全に理解する

# 更新履歴

**2022/05/21 更新**

- UnityとXcodeのバージョンを更新
    - Unity 2020.3.34f1
    - Xcode 13.4
- 各サンプルプロジェクトのブランチ及びリンクを変更 (内容自体は変わりません)
- サンプルコードの型を明示的に変更
    - e.g. `int` → `Int32`
- **<font color="Red">Swiftだけでプラグイン実装を完結できることが分かったので以下の章を更新</font>**
    - **[Swiftで実装しないのか？](#swiftで実装しないのか)**
    - [Swiftでのサンプル実装](#swiftでのサンプル実装)
    - [`ObjC/ObjC++`と`Swift`のどちらで実装すればよいか？](#objcobjcとswiftのどちらで実装すればよいか)

----

最近、iOSネイティブアプリ開発に触れる機会が多く、それと合わせてUnity向けのiOSネイティブプラグインを実装する機会もあったので、これを機に備忘録として知見を纏めてみようと思います。

正直ここで解説する情報自体に目新しいものは無く、基本的には調べれば出てくる情報が多いかもしれません。
ただ、iOSネイティブアプリ開発及びUnity向けのプラグイン実装に於いて全くの初学者である自分が **「調べている上で色々と疑問に思ったポイントや気付いた点」** は少なからずとも出てきたので、そこらを補足しつつ体系的に解説していければと思います。


:::note info
NOTE: **「完全に理解した」の意味について**

タイトルに **「完全に理解する」** とありますが、ここで言う"完全に理解"は **「実装するためのチュートリアルを完了出来た」までの意味**を指します。
参考: https://twitter.com/ito_yusaku/status/1042604780718157824?s=20
:::

**バージョン**

- Unity 2020.3.34f1
- Xcode 13.4



# この記事で解説する内容について

本記事では以下の目次順をベースに所々に補足を交えつつ解説していきます。

- **最小構成から見るネイティブプラグイン基礎**
    - 例として「"Hello World"とログ出力しつつ、戻り値として整数型を返すプラグイン」を実装
        - Objective-C++の実装例
        - Swiftの実装例
- **幾つかの実装例**
    - ネイティブコード側でインスタンス化したオブジェクトの管理
    - ネイティブコードからC#のメソッドを呼び出す

他にも本記事の大きめな補足として以下の付録記事も用意してます。
こちらについては、ある程度本文を読み進めていく上で得られる予備知識を前提に書いているので、先ずは本文に沿って読んでいくのをオススメします。

- [Objective-C++について](https://qiita.com/mao_/items/ca5af02534c3ca3715c7)
- [ネイティブプラグインの設計について](https://qiita.com/mao_/items/15d05d25a99ab290fa50)
- [ネイティブプラグインを実装するには](https://qiita.com/mao_/items/88aeac26adb17194ebdc)
- [Xcodeの設定を自動化する](https://qiita.com/mao_/items/c678f93ee04608492788)



## 記事のターゲット

基本的には初学者をターゲットとしており、主に以下の方を想定してます。

- **iOSビルドまでは出来るが、ネイティブプラグイン開発については全然分からん**
- **何回か作ったことあるけど、コピペに近い雰囲気レベルの理解なのでちゃんと把握しておきたい**

### この記事の目的

上記に該当する方がiOSネイティブプラグインを実装する上で必要となる知識(iOSネイティブ周りの文脈/実装の流れ/etc..)を広く把握し、自分で実装したり深く調べていく際の足がかりとするところまでを目的としてます。


## 解説しない内容

記事を読み進める上では以下の前知識はある程度前提としてます。
ここらについては記事中では解説しないのでご了承下さい。

- **Unityの基本的な操作**
    - e.g. Scriptの追加や実行方法、ビルド方法など
- **C# 基礎**
    - ※但しP/Invokeと言ったプラグイン開発に関わってくるであろう箇所は記事中でも触れていく
- **iOSビルド/実機動作までの手順**


### 追記するかも..?

今は解説してませんが...元気があったら追記するかもです...
今は他の記事を参照して頂ければと思います... :bow: 

- Objective-Cの構文について
- Swiftの構文について
- マーシャリング周り



## 記事中で解説する言語について

記事中ではiOSのネイティブプラグインとして実装される「ネイティブ側のコード全般」を「**ネイティブコード**」と言う呼び名で記載していきます。

解説するネイティブコードは主に以下のものとなります。[^5]
合わせて`拡張子`と`記事中での略称`の対応表を載せておきます。

|言語|略称|拡張子|
|:-:|:-:|:-:|
|**Objective-C**|ObjC|.m|
|**Objective-C++**|ObjC++|.mm|
|**Swift**|Swift|.swift|

[^5]: 正確に言うとC言語/C++も含まれてくるかと思われるが、基本的には挙げている言語をベースに解説


### 【補足】 `ObjC`と`ObjC++`の違いについて

初学者の方の中には「`Objective-C`は聞いたことあるけど、`Objective-C++`は聞いたことないかも」という方も居るかもしれないので簡単な解説を以下の記事に纏めてみました。
気になる方はこちらを御覧ください。

- [Objective-C++について](https://qiita.com/mao_/items/ca5af02534c3ca3715c7)


<details><summary><b>【余談】 拡張子の`.m`の意味について (クリックで展開)</b></summary><div>

ふと「ObjCの拡張子の`.m`って何の略？」「言語名と`m`って一文字もかぶってなくない？どこから出てきたの？」と思って調べてみたところ...「メソッドの略」「メッセージの略」と様々な説が出てきた。
かなり曖昧な感じだったので、気にしないほうが良さそう。。

- [なぜObjective-Cの拡張子が”m”なのか？](https://www.mindto01s.com/2013/01/13/why_objc_extension_is_m.html)
- [Why do Objective-C files use the .m extension?](https://stackoverflow.com/questions/652186/why-do-objective-c-files-use-the-m-extension)

---

</div></details>










# 最小構成から見るネイティブプラグイン基礎

先ずは基礎として最小構成のネイティブプラグインをベースに概要を解説していきます。

実装するプラグインは **「"Hello World"とログ出力しつつ、戻り値として整数を返す」** だけのシンプルな物であり、**ネイティブコード側は`ObjC++`で実装していきます。**

ここでは基礎となる以下のポイントを理解するのが目的です。

- 一通りの実装の流れ
    - ネイティブコードをC#から呼び出すための関数の外部宣言(`extern "C"`)
- C#から`P/Invoke`で呼び出す際のポイント


## Swiftで実装しないのか？

いきなりObjC++と聞いて、恐らくはこの疑問を抱く方がいるかもしれません。
更に言うと**Swiftだけでプラグインの実装を完結させることも可能です。** (自分もこの記事の投稿後に知った事実...)

- 参考: [Swift だけで Unity の iOS の Native Plugin を作る](https://qiita.com/fuziki/items/955c2b35514bcfc37969)

それに伴ってこの記事もSwift完結ベースで書き直そうかと考えたのですが...まだObjC++で保守されているであろう現役のプラグインも存在するかと思われるので、記事の内容自体はObjC++ベースのままから書き直さずに、代わりに以下の追記記事を投稿しました。

- [サンプルコードをSwiftだけで完結出来るように置き換える](https://qiita.com/mao_/items/07466e169d08cbeff221)

ただ、あくまで本編を前提とした追記記事となるために、先ずはこちら本編を俯瞰した上で全体の流れを掴んでから読んでみることをオススメします。

<details><summary>この章の過去の内容はこちら (クリックで展開)</summary><div>

しかし、Swiftで実装するにあたっては以下の要件が必要であり、ObjC++と比べると少し手間と言うか...複雑になっているため、先ずは構成がシンプルに済むObjC++の解説から入っていきます。

- **C#からSwiftのコードを直接呼び出すことは出来ず、間にObjC++が挟まる** 
- **ビルド結果に対して、ある一定の設定を適用するEditor拡張を実装する必要がある**
    - その設定も[Unityのバージョンによって変わってくる](https://qiita.com/mao_/items/2305822054dadb2c5da8)側面がある

Swift編についてはObjC++編で必要な予備知識を解説した後に入っていきます。

---

</div></details>





## この章のサンプルプロジェクト

サンプルプロジェクトは以下のリポジトリの`objCpp/minimumExample`ブランチにあります。

- [mao-test-h/UnityNativePlugin-for-iOS-Examples - objCpp/minimumExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/objCpp/minimumExample)

```:プロジェクト構成
[Assets]
    L [MinimumExample]    // この章で解説するサンプル一式
        L [Plugins]
            L [iOS]
                L Example.mm
        L [Scenes]
            L MinimumExample.unity
        L [Scripts]
            L Exsample.cs
```

`MinimumExampleシーン`にはuGUIで実装されたボタンが一つ配置されており、それを押下することでネイティブプラグインを呼び出す構成になってます。

各ソースの役割としては以下のようになります。(関連性と言った詳細は後述)

- `Scripts/Exsample.cs`
    - シーン中のGameObjectにアタッチされており、ボタン押下時にネイティブプラグインの処理を実行
- `Plugins/iOS/Exsample.mm`
    - ObjC++で実装されたネイティブコード[^2]

[^2]: ひょっとしたら「あれ？ヘッダーファイルは？」と思う方も居るかもいるかもしれませんが、無いのには理由があります。詳細は後述。


### 実装の流れ

プラグインの実装の流れとして、以下の順に解説していきます。

**▼ ネイティブコードの実装**

- **①. ObjC++で必要な処理を実装 (クラスの定義/実装など)**
    - → 今回で言うと「"Hello World"とログ出力しつつ、戻り値として整数を返す」処理を実装
- **②. ①の処理をC#から呼び出せるようにする**

**▼ C#からの呼び出し**

- **③. C#から`P/Invoke`で②の処理を呼び出す**
    - ※「`P/Invoke`とは？」と言う方は[こちらの章](#pinvokeについて)を参照


### ネイティブコードの実装

該当するソースは`Example.mm`です。
ソース全体はこちら。

- [objCpp/minimumExample - Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/minimumExample/Assets/MinimumExample/Plugins/iOS/Example.mm)


#### ■ ①. ObjC++で必要な処理を実装 (クラスの定義/実装など)

先ずはネイティブコード側でプラグインの目的である **「"Hello World"とログ出力しつつ、戻り値として整数を返す」** 処理を実装していきます。

ソース中で言うと以下の箇所が該当する部分であり、要点を纏めると以下のようになります。

- `@interface Example : NSObject` ~ `@end`で囲まれた部分が**クラスの宣言部**
    - コメントにある通り、`printHelloWorld`は**クラスメソッド(静的関数)** として宣言してます
- `@implementation Example` ~ `@end`で囲まれた部分が**クラスの実装部**
    - `printHelloWorld`の実装箇所では **「Hello World」とログ出力しつつ、戻り値として`2`を返してます**

```obj-c++:Example.mm
#import <Foundation/Foundation.h>

// MARK:- interface (クラスの宣言部)

@interface Example : NSObject

/// ログに"Hello World"と出力して2を返す。
/// NOTE: ここではクラスメソッド(静的関数)として実装
+ (int32_t)printHelloWorld;

@end


// MARK:- implementation (クラスの実装部)

@implementation Example

+ (int32_t)printHelloWorld {
    // ログ出力
    NSLog(@"Hello World");

    // 戻り値を返す
    return 2;
}

@end
```

一先ずはプラグインの肝となる機能は用意できました。
あとは **「`printHelloWorld`メソッドをC#から呼び出すことが出来れば目的達成」** と言ったところですが...**これだけだとC#から呼び出すことは出来ません。**

では **「どうやったらC#から呼び出せるようになるのか？」** について、次で解説していきます。


##### 【補足】 `NSObject`とは？

しれっと`NSObject`と言うクラスを継承してますが、この`NSObject`については以下の章で簡単に補足してます。
気になる方は御覧ください。

- [NSObjectについて](#nsobjectについて)


#### ■ ②. ①の処理をC#から呼び出せるようにする

「ではどうやったらC#から`Example`クラスのメソッドを呼び出せるのか？」と言う話についてですが、**前提としてC#からネイティブコードを呼び出す際には`extern "C"`で外部宣言されている関数である必要があります。**

ソース中から該当箇所を切り出すと以下の部分であり、`extern "C"`で囲われている`int32_t printHelloWorld()`と言う関数が**実際にC#から`P/Invoke`で呼び出される関数になります。**
この外部宣言している関数内では**ObjC++で実装したクラスを呼び出すことが可能**となるために、この関数を経由して`Example`クラスのメソッドを呼び出します。

```obj-c++:Example.mm
// MARK:- extern "C" (Cリンケージで宣言)

#ifdef __cplusplus
extern "C" {
#endif

// NOTE: この関数が実際にUnity(C#)から呼び出される
int32_t printHelloWorld() {

    // `Example.printHelloWorld`を呼び出す。呼び出し時の構文はObjC形式となる。
    // NOTE: クラスメソッド(静的関数)として実装しているので、クラスをインスタンス化せずに直接呼び出せる
    return [Example printHelloWorld];
}

#ifdef __cplusplus
}
#endif
```

ちなみに「ObjC++のクラス実装」と「外部宣言関数」は同一ソース内にて実装可能となるので、今回の例では`Example.mm`の1ソースだけで済んでます。

##### 【補足】 `extern "C"`で外部宣言を行う必要がある理由

`extern "C"`で外部宣言を行う必要がある理由については以下の記事で解説してます。

- [P/InvokeでObjC++(.mm)のコードを呼び出すならextern "C"を指定して外部宣言する必要がある](https://qiita.com/mao_/items/ca5af02534c3ca3715c7#pinvoke%E3%81%A7objcmm%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%E3%81%AA%E3%82%89extern-c%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E5%A4%96%E9%83%A8%E5%AE%A3%E8%A8%80%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%8C%E3%81%82%E3%82%8B)



### C#からの呼び出し

該当するソースは`Example.cs`です。
ソース全体はこちら。

- [objCpp/minimumExample - Example.cs](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/minimumExample/Assets/MinimumExample/Scripts/Example.cs)


#### ■ ③. C#から`P/Invoke`で②の処理を呼び出す

こちらは`P/Invoke`を用いて②で外部宣言した関数を呼び出すだけです。

以下のコードで言う`static extern Int32 PrintHelloWorld()`を呼び出したら **`Example.mm`にある`int32_t printHelloWorld()`が呼び出されます。**

```csharp:Example.cs
        // ObjectiveC++コードで実装した`Example`クラスのP/Invoke

        /// <summary>
        /// `printHelloWorld`の呼び出し
        /// </summary>
        /// <remarks>
        /// NOTE: Example.mmの「extern "C"」内で宣言した関数をここで呼び出す
        /// - iOSのネイティブプラグインは静的に実行ファイルにリンクされるので、`DllImport`にはライブラリ名として「__Internal」を指定する必要がある
        /// - `EntryPoint`に.mm側で宣言されている名前を渡すことでC#側のメソッド名は別名を指定可能
        /// </remarks>
        [DllImport("__Internal", EntryPoint = "printHelloWorld")]
        static extern Int32 PrintHelloWorld();
```

あとは上記で定義したメソッドをuGUIのボタン押下時に呼び出すようにすれば完了です。

```csharp:Example.cs
            _buttonHelloWorld.onClick.AddListener(() =>
            {
#if !UNITY_EDITOR && UNITY_IOS
                // ネイティブプラグインの呼び出し
                var ret = PrintHelloWorld();
                Debug.Log($"戻り値: {ret}");
#else
                // それ以外のプラットフォームからの呼び出し (Editor含む)
                Debug.Log("Hello World (iOS以外からの呼び出し)");
#endif
            });
```


### ここまでのまとめ

以上が一通りの実装の流れになります。
ここまでの流れの振り返りとして、**C#からネイティブプラグインを呼び出す流れ**を図に示すと、以下のようなイメージになります。

![relation_chart_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/3164a3d8-b9ac-5a69-3612-6454abcd14c0.png)

あとは補足として以下の章を記述しているので、宜しければこちらも御覧ください。

- [ヘッダーファイルは必要？](#ヘッダーファイルは必要)
- [.m(ObjC)だけでは実装できないのか？ → .mm(ObjC++)を使わない例](#mobjcだけでは実装できないのか--mmobjcを使わない例)
    - [.mと.mmの使い分けについて](#mとmmの使い分けについて)


### 実は省略できる部分がある

解説したサンプルですが、**実は省略できる部分があります。**
今回の要件程度であれば、以下の様に①の手順を抜かして<font color="Red">外部宣言関数だけで完結させることが可能です。</font>

```objc++:Example.mm
// MARK:- extern "C" (Cリンケージで宣言)

#ifdef __cplusplus
extern "C" {
#endif

// NOTE: この関数が実際にUnity(C#)から呼び出される
int32_t printHelloWorld() {

    // NOTE: [Example printHelloWorld]を実装して呼び出さずに、直接相応の処理を呼び出す

    // ログ出力
    NSLog(@"Hello World");
    // 戻り値を返す
    return 2;
}

#ifdef __cplusplus
}
#endif
```

#### 簡単な呼び出し程度であれば外部宣言関数内で完結可能

今回は一通りの流れに触れておくために敢えて手順①を含めて解説しましたが、**実はこちらの手順は慣習的に行う必要はありません。**
<font color="Red">簡単な処理の呼び出し程度であれば**外部宣言関数内だけで完結させることが可能です。**</font>

では具体的に **「どういったプラグインを作る際に外部宣言関数だけで完結させることが可能か？」** と言うと、個人的には以下の要件を満たす場合には外部宣言関数だけでも完結できるかなと思ってます。

- **静的変数/静的関数として実装されているiOSネイティブAPI/ライブラリの呼び出し**
- **その他、状態を持たずに呼び出せそうなネイティブAPI/ライブラリの呼び出し**

逆に **「メンバ変数などに状態を持つ必要があり、素直にクラスで実装したほうが良さそう」** ならObjC++(若しくは次で解説するSwift)でクラスを実装した上で呼び出すようにしたほうが良いかもです。[^6]

[^6]: 一応グローバル変数に状態を持つと言う手もある。複雑化しない程度であればこれでも良いかもしれない。

#### 幾つかの実装例

上記の流れを踏まえて、**外部宣言関数だけで完結している**幾つかの実装例を用意しました。
宜しければ参考にしてみてください。

- [Unity上からiOS端末の発熱状態を取得する](https://gist.github.com/mao-test-h/f7a6733fc3eea2b73f617717c82d0351)
- [Unity上からiOS端末のダークモード判定を行う](https://gist.github.com/mao-test-h/b1cea20820832499a7e7942589db7e48)
- [Unity上からiOS端末のシステムボリュームを取得する](https://gist.github.com/mao-test-h/f2711376037dd77fcfa6e835fc2268eb)
- [Unity上からiOS端末の画面輝度を取得/設定](https://gist.github.com/mao-test-h/95b8de9147c95a58570683ce67b864de)





## Swiftでのサンプル実装

**追記:** [Swiftで実装しないのか？](#swiftで実装しないのか)の章でも記載したとおりに、**Swiftだけでプラグインの実装を完結できることが分かりました。**

なので、こちらの章の内容自体は**これから新規で実装を行うにあたっては不要(と言うか冗長)** になるかと思われますが、まだこの手法で保守されているプラグインも存在するかと思われるので、章自体は消さずに残しておきます。


**<font color="Red">以降、この章についてはそれを踏まえた上で御覧ください。</font>**

---

ここまではネイティブコードを`ObjC++`のみで完結させる場合の解説でした。

とは言え、今現在に於いては **「もしネイティブコード側でクラスを実装するなら[^7]」** 保守観点から`Swift`で実装したい場合もあるかと思います。
ここでは最小構成サンプルの要件自体はそのままに、**クラスの実装をSwiftに置き換えて解説していきます。**

[^7]: 上述した通り、そもそも外部宣言関数の呼び出しだけで完結できるならSwiftは不要となる。

Swiftを組み込むにあたって、以下のポイントを理解するのが目的です。

- Swiftで実装できる箇所の把握
- Swiftを実装するにあたって必要な設定


### この章のサンプルプロジェクト

サンプルプロジェクトは以下のリポジトリの`swift/minimumExample_old`ブランチにあります。

- [mao-test-h/UnityNativePlugin-for-iOS-Examples - swift/minimumExample_old](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/swift/minimumExample_old)


```:プロジェクト構成
[Assets]
    L [MinimumExample-Swift]    // この章で解説するサンプル一式
        L [Plugins]
            L [iOS]
                L Example.mm
                L Example.swift
        L [Scenes]
            L MinimumExample_Swift.unity
        L [Scripts]
            L [Editor]
                L XcodePostProcess.cs
            L Exsample.cs
```

名前などが変わってますが、シーンの構成自体は[前の章](#この章のサンプルプロジェクト)と同じです。
新規で追加されている`XcodePostProcess.cs`と言うEditor拡張については後述します。


### 実装の流れ

大まかに言えば、[前の章の流れ](#実装の流れ)で言うところの①の部分が**Swiftの実装に置き換わるイメージになります。**
あとは②の外部宣言に関しても、Swiftの呼び出しに合わせて多少変更が入ってきます。

呼び出しの流れを図に示すと、以下のようなイメージになります。

![relation_chart_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/71684901-eff1-6f9e-5d28-9aa27b7c7ea2.png)

C#からの呼び出しについては変更点が無いので、それを踏まえて以下の順に解説していきます。
(③は新規で増える手順となります。詳細は後述)

**▼ ネイティブコードの実装**

- **<font color="Red">①. Swiftで必要な処理を実装 (クラスの実装など)</font>**
    - → 今回で言うと「"Hello World"とログ出力しつつ、戻り値として整数を返す」処理を実装
- **②. ①の処理をC#から呼び出せるようにする**

**▼ Editor拡張の実装**

- **③. `[PostProcessBuild]`でSwiftのバージョン指定を自動化**



#### ■ ①. Swiftで必要な処理を実装 (クラスの実装など)

Swiftで「"Hello World"とログ出力しつつ、戻り値として整数を返す」処理を実装していきます。
ほぼほぼ[前の章のObjC++での実装](#--objcで必要な処理を実装-クラスの定義実装など)をSwiftに置き換えるイメージになります。

詳細は次で解説しますが、**こちらのSwiftで実装したクラスは最終的にはObjC++のソースファイル(`.mm`)から呼び出す必要があるために、** 実装する上では以下のポイントを守る必要があります。

- **アクセスレベルを`public` or `open`に設定**
    - 参考: [[Swift]アクセス修飾子](https://qiita.com/GATA/items/90341f56a56ff584e756)
- **公開するクラスには`NSObject`を継承させる**
    - ※`NSObject`を継承させないやり方もある。詳しくは以下の記事を参照
        - 参考: [[Swift] SwiftとObjective-Cで相互に連携する -> NSObjectを継承していないクラスをObjective-C側で利用する](https://qiita.com/edo_m18/items/861d090a5471f4f0eeae#nsobject%E3%82%92%E7%B6%99%E6%89%BF%E3%81%97%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92objective-c%E5%81%B4%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B)
- **公開するメソッドには`@objc`を付ける**
    - → 下の例では`printHelloWorld`メソッドに付けている

```swift:Example.swift
import Foundation

// NOTE: 
// - ObjCに公開する場合にはアクセスレベルを`public` or `open`に設定する必要がある
// - ObjCに公開するクラスには`NSObject`を継承させる
// - ObjCに公開するメソッドには`@objc`を付ける
public class Example : NSObject {

    /// ログに"Hello World"と出力して2を返す。
    /// NOTE: ここではクラスメソッド(静的関数)として実装
    ///
    /// - Returns: 2固定
    @objc public static func printHelloWorld() -> Int32 {
        // ログ出力
        print("Hello World")

        // 戻り値を返す
        return 2
    }
}
```

**※追記**: どうやらSwift4以前では`NSObject`を継承したクラスやdynamicは暗黙的に`@objc`が付けられていたが、Swift4以降からは付与されなくなる仕様変更があった模様
→ 参考: [Swiftのattributeまとめ[Swift4対応] - @objc](https://qiita.com/shtnkgm/items/a793f26445f2b8390bee#objc)


#### ■ ②. ①の処理をC#から呼び出せるようにする

こちらも前回同様に~~Swiftで実装したクラスだけだとC#から呼び出すことが出来ない~~ので、`printHelloWorld`を呼び出すにはObjC++のソースファイル(.mm)にて外部宣言した関数を経由して呼び出す必要があります。
~~※ちなみに外部宣言はC言語/C++の機能となるために、Swiftのソースコード上から外部宣言するのは不可能と言う認識~~

こちらもほぼ[前の章で解説した外部宣言](#--の処理をcから呼び出せるようにする)と変わりありませんが、**Unity2019.3以降は`#import <UnityFramework/UnityFramework-Swift.h>`を追加する必要があります。**

```obj-c++:Example.mm
#import <Foundation/Foundation.h>

// 2019.3からはこちらをimportする必要がある
#import <UnityFramework/UnityFramework-Swift.h>

// MARK:- extern "C" (Cリンケージで宣言)

#ifdef __cplusplus
extern "C" {
#endif

// NOTE: この関数が実際にUnity(C#)から呼び出される
int32_t printHelloWorld() {

    // `Example.swift`で実装した`Example.printHelloWorld`を呼び出す。呼び出し時の構文はObjC形式となる。
    // NOTE: クラスメソッド(静的関数)として実装しているので、クラスをインスタンス化せずに直接呼び出せる
    return [Example printHelloWorld];
}

#ifdef __cplusplus
}
#endif
```



#### ■ ③. `[PostProcessBuild]`でSwiftのバージョン指定を自動化

UnityがiOSビルドで出力するXcodeプロジェクトファイル(`.xcodeproj`)は**内部的に設定されているSwiftのバージョンが古い時があり、**利用するXcodeのバージョンが新しかったりすると**`Unspecified`扱いになることがあります。**

なので、`[PostProcessBuild]`から利用するSwfitバージョンを明示的に指定するEditor拡張を用意しておくと便利です。[^8]
→ この例では`5.0`を指定

[^8]: これはどちらかと言うとオプション的な設定であり、必須項目では無い。ちなみにこの設定自体は`.xcodeproj`を開いてから手動で設定を変更することも可能。

```csharp:XcodePostProcess.cs
        /// <summary>
        /// Swiftを実装するにあたって必要な設定を自動で適用する
        /// </summary>
        [PostProcessBuild]
        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));

            // 2019.3からは`UnityFramework`に分離しているので、targetGuidはこちらを指定する必要がある。
            // NOTE: 前バージョンと共存させたい場合には「#if UNITY_2019_3_OR_NEWER」で分けることも可能
            var targetGuid = project.GetUnityFrameworkTargetGuid();

            // Swift version: 5.0
            // 
            // NOTE:
            // Unityのバージョンによっては?生成されるxcodeprojに古いSwiftのバージョンが指定されてるせいで、
            // 開くXcodeが新しかったりすると`Unspecified`扱いになるので一応は明示的に指定しておく
            project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");

            File.WriteAllText(projectPath, project.WriteToString());
        }
```

##### 【補足】 Xcodeの設定の自動化について

この様にXcodeプロジェクトファイルの設定はUnityの`PostProcessBuild`を使うことで自動化することが可能です。
更に詳しい話については以下の別記事にて纏めているので、宜しければこちらを御覧ください。

- [Xcodeの設定を自動化する](https://qiita.com/mao_/items/c678f93ee04608492788)


## ここまでのまとめ

以上が最小構成を題材にした実装例となります。
とりあえずは以下の要点を掴んでおけばOKです。

- **ネイティブコード**
    - **外部宣言されている関数が実際にC#から呼び出される**
        - <font color="Red">簡単なiOSネイティブAPI/ライブラリの呼び出し程度であれば、外部宣言した関数内だけで完結可能</font>
        - クラスを実装する場合には`ObjC/ObjC++` or `Swift`で実装した上で、最終的にはこちらから呼び出しを行う
- **C#**
    - **ネイティブコード側で外部宣言されている関数を`P/Invoke`で呼び出すだけ**


### 【補足】 ネイティブプラグインを実装する上での設計について

今回解説した最小構成のサンプルは**運用/保守を見越した実装にはなってません。**
(C#からの呼び出し時の処理とかプリプロセス命令で愚直に分岐して直に呼び出したりしている)

ここらの詳細については、以下の記事で別途補足します。

- [ネイティブプラグインの設計について](https://qiita.com/mao_/items/15d05d25a99ab290fa50)


### 【補足】 ネイティブコードってどうやって実装していけばよいのか？

ここまでの流れで取り扱っていない範囲として、そもそもとして **「ネイティブコードってどう実装していけば(効率含めて)良いのか？」** というのがあります。

例えば一例として「Assets配下に`.mm`を直接置いてコーディング → Unityでビルドしてネイティブプラグインの動作確認」と言う流れでも出来なくはないですが...動作確認の効率としては少し悪いです。

自分のやり方にはなりますが、以下の記事で別途補足してます。

- [ネイティブプラグインを実装するには](https://qiita.com/mao_/items/88aeac26adb17194ebdc)







# 幾つかの実装例

上述の最小構成の例では「ネイティブコード側のクラスメソッド(静的関数)を呼び出す」 or 「外部宣言関数だけで完結」と言った比較的シンプルな例でしたが、実際にネイティブプラグインを利用する上では **「ネイティブコード側で実装しているクラス(以降、`ネイティブクラス`と表記)をインスタンス化したい」「更にインスタンス化したオブジェクトのインスタンスメソッドを呼び出したい」** と言った要件が出てくるかと思います。

この章では以下の要件について解説していきます。

- **ネイティブコード側でインスタンス化したオブジェクトの管理**
- **ネイティブコードからC#のメソッドを呼び出す**

サンプルコードは`ObjC++`で解説します。





## ネイティブコード側でインスタンス化したオブジェクトの管理

表題の件について、C#からの呼び出しを踏まえると **「そもそもインスタンス化したオブジェクトはどうやって管理するのか？」「C#側でインスタンス化したオブジェクトを管理できるのか？」** と言った疑問が出てくるかと思います。(実際に自分は出てきた)

この章では以下のポイントを理解するのが目的です。

- ネイティブコード側でインスタンス化したオブジェクトをC#上で管理する方法
    - インスタンスの生成と解放について
- C#上で管理しているネイティブコードのオブジェクトからインスタンスメソッドを呼び出す方法


### この章のサンプルプロジェクト

サンプルプロジェクトは以下のリポジトリの`objCpp/instanceMethodExample`ブランチにあります。

- [mao-test-h/UnityNativePlugin-for-iOS-Examples - objCpp/instanceMethodExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/objCpp/instanceMethodExample)


基本的な構成は[最小構成サンプル](#最小構成から見るネイティブプラグイン基礎)とさほど変わりはありませんが、新たに以下の変更を加えてます。

- シーン中に数値入力用の`InputField`を配置
- `Hellow World`ボタンを押したら`"Hello World : [(InputFieldで入力した数値)]"`と言う文字列を出力しつつ、入力値を戻り値として返す


### 実装のポイント

ソース中から必要な箇所を掻い摘んで解説していきます。
全ソースコードについては以下を参照。

- [objCpp/instanceMethodExample - Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/instanceMethodExample/Assets/InstanceMethodExample/Plugins/iOS/Example.mm)
- [objCpp/instanceMethodExample - Example.cs](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/instanceMethodExample/Assets/InstanceMethodExample/Scripts/Example.cs)


#### ■ ネイティブコード側でインスタンス化しつつ、インスタンス自体はC#側で持つ

ネイティブクラスのインスタンス化はネイティブコード側で担当しつつも、**そのインスタンス自体のポインタは戻り値としてC#側に返すようにします。**

**C#側ではこの戻り値のポインタを[IntPtr型](https://docs.microsoft.com/ja-jp/dotnet/api/system.intptr?view=net-5.0)として受け取ってインスタンスの管理を行います。**

※ちなみにサラッと出ていている`CFRetain`と、この後出てくる`CFRelease`については[こちらの章](#ネイティブコード側のインスタンスの生成解放時の処理についての補足)で補足してます。


```obj-c++:Example.mm
extern "C" {
#endif

// インスタンス化
// NOTE: 戻り値のポインタをC#側でIntPtrなどで保持し、インスタンスメソッドの呼び出し時に渡して使う
Example* createExample() {
    Example* instance = [[Example alloc] init];
    CFRetain((CFTypeRef) instance);
    return instance;
}

#ifdef __cplusplus
}
#endif
```

```csharp:Example.cs
        // ネイティブコード側で作られたインスタンスのポインタを保持
        IntPtr _instance = IntPtr.Zero;

        void Start()
        {
#if !UNITY_EDITOR && UNITY_IOS
            _instance = CreateExample();
#endif
        }

        #region P/Invoke

        // ObjectiveC++コードで実装した`Example`クラスのP/Invoke

        // ネイティブコード側にあるExampleクラスのインスタンス化
        // NOTE: 戻り値はインスタンスのポインタ
        [DllImport("__Internal", EntryPoint = "createExample")]
        static extern IntPtr CreateExample();

        #endregion P/Invoke
```


#### ■ 解放時には保持しているポインタを渡してネイティブコード側で解放

インスタンス化がそうであるように、**解放処理自体もネイティブコード側で行います。**
具体的には以下のようにC#から解放処理を呼び出す際にポインタを渡すことで、実際の解放処理はネイティブコード側で行うようにします。

今回の例だと簡易的なサンプルなのでC#側は`OnDestroy()`で解放処理を行ってますが、実際に利用するにあたっては`IDisposable`辺りを実装して解放処理を呼び出す形にするのが良いかもしれません。

```obj-c++:Example.mm
extern "C" {
#endif

// 解放
void releaseExample(Example* instance) {
    CFRelease((CFTypeRef) instance);
}

#ifdef __cplusplus
}
#endif
```

```csharp:Example.cs
        // ネイティブコード側で作られたインスタンスのポインタを保持
        IntPtr _instance = IntPtr.Zero;

        void OnDestroy()
        {
            if (_instance != IntPtr.Zero)
            {
                ReleaseExample(_instance);
            }
        }

        #region P/Invoke

        // ObjectiveC++コードで実装した`Example`クラスのP/Invoke

        // インスタンスの解放
        [DllImport("__Internal", EntryPoint = "releaseExample")]
        static extern void ReleaseExample(IntPtr instance);

        #endregion P/Invoke
```


#### ■ インスタンスメソッドの呼び出しも同様に、C#側で保持しているポインタを渡してネイティブコード側で呼び出す

インスタンスメソッドの呼び出しについても、C#からはインスタンスのポインタを渡すだけであって、**実際の処理はネイティブコード側で呼ぶようにします。**
今回の例では「インスタンスメソッドを呼び出すための外部宣言関数」を用意しつつ、引数から受け取ったインスタンスのポインタを利用して間接的にインスタンスメソッドを呼び出します。

※ちなみにサンプル中でObjC++で実装されている`Example`クラスについては以下を参照。

<details><summary><b>Exampleクラスについて (クリックで展開)</b></summary><div>

`MinimumExample`にあった`printHelloWorld`と言うクラスメソッドを廃止しつつ、インスタンスの利用であることを分かりやすくするためにインスタンスメソッドを追加してます。

- `(void)setMember:(int32_t)value;`
    - 引数として受け取った`value`をメンバ変数である`_member`に格納
- `(int32_t)printHelloWorldWithMember;`
    - ログに`"Hello World : [(InputFieldで入力した数値)]"`と出力しつつ、`_member`を戻り値として返す

コード全体は以下。

```obj-c++:Example.mm
@interface Example : NSObject

// メンバ変数に値を設定
- (void)setMember:(int32_t)value;

// ログに"Hello World"と出力してメンバ変数(_member)に設定された値を返す
- (int32_t)printHelloWorldWithMember;

@end


// MARK:- implementation (クラスの実装部)

@implementation Example {

    // メンバ変数
    int32_t _member;
}

// イニシャライザ(インスタンスの初期化)
- (instancetype)init {
    self = [super init];
    if (self) {
        _member = 0;
    }

    return self;
}

- (void)setMember:(int32_t)value {
    _member = value;
}

- (int32_t)printHelloWorldWithMember {
    // ログ出力
    NSLog(@"Hello World : [%d]", _member);

    // 戻り値を返す
    return _member;
}

@end
```

---

</div></details>


```obj-c++:Example.mm
#ifdef __cplusplus
extern "C" {
#endif

// 以下はインスタンスメソッドの呼び出し
// NOTE: 第一引数にはインスタンス化した際に保持しているポインタを渡す

void setMember(Example* instance, int32_t value) {
    [instance setMember:value];
}

int32_t printHelloWorldWithMember(Example* instance) {
    return [instance printHelloWorldWithMember];
}

#ifdef __cplusplus
}
#endif
```

```csharp:Example.cs
        // ネイティブコード側で作られたインスタンスのポインタを保持
        IntPtr _instance = IntPtr.Zero;

        void Start()
        {
            _buttonHelloWorld.onClick.AddListener(() =>
            {
#if !UNITY_EDITOR && UNITY_IOS
                // ネイティブプラグインの呼び出し
                var ret = PrintHelloWorldWithMember(_instance);
                Debug.Log($"戻り値: {ret}");
#else
                // それ以外のプラットフォームからの呼び出し (Editor含む)
                Debug.Log("Hello World (iOS以外からの呼び出し)");
#endif
            });

            _inputField.onEndEdit.AddListener(text =>
            {
                if (int.TryParse(text, out var num))
                {
#if !UNITY_EDITOR && UNITY_IOS
                    // ネイティブプラグインの呼び出し
                    SetMember(_instance, num);
#else
                    Debug.Log($"{num} (iOS以外からの呼び出し)");
#endif
                }
            });

            ........
        }

        #region P/Invoke

        // `setMember`の呼び出し
        [DllImport("__Internal", EntryPoint = "setMember")]
        static extern void SetMember(IntPtr instance, Int32 value);

        // `printHelloWorldWithMember`の呼び出し
        [DllImport("__Internal", EntryPoint = "printHelloWorldWithMember")]
        static extern Int32 PrintHelloWorldWithMember(IntPtr instance);

        #endregion P/Invoke
    }
}
```

##### 【補足】 C#側でネイティブコードから渡って来るポインタをIntPtr型で受け取る必要性について

この章の実装ではネイティブコードの戻り値であるポインタ型は以下のように**IntPtr型**として受け取ってますが、

> [DllImport("__Internal", EntryPoint = "createExample")]
> static extern IntPtr CreateExample();

実はと言うと(?)、こちらの値は **`unsafe`コンテキストが有効な箇所**であれば、以下のように**任意のポインタ型として受け取ることが可能です。**
→ この例では汎用ポインタ(`void*`)で受け取っているが、例えば戻り値が[Blittable型](https://docs.microsoft.com/ja-jp/dotnet/framework/interop/blittable-and-non-blittable-types)に準拠した構造体のポインタとかであれば、そのままC#上からポインタに直にアクセスとかも可能

> [DllImport("__Internal", EntryPoint = "createExample")]
> static extern void* CreateExample();

**▼ 安易にunsafeを使うのは危険**

ただ、これらの操作については当然ながら**unsafeの危険性**が付き纏う上で、呼び出し箇所の方でも`unsafe`コンテキストが必要になったりと保守性の観点から見ると治安が悪くなっていきます。
(unsafeに伴う不正なメモリ操作による事故などの懸念がある上で、そもそもC#上からポインタアクセスする必要が無い箇所でもunsafeが使われていたりすると「これどっかでunsafeな操作が行われているのか..?」と可読性的なところで紛らわしくなったりもするので)

**▼ 今回のようなケースならIntPtr型で十分**

なので今回のケースのような **「インスタンスの保持と受け渡し」 と言った操作が主**であり、C#上からポインタアクセスする必要が無い場合に於いては、**IntPtr型を用いることでunsafeコンテキストが必要無くなる上に、それ自体が[Opaque pointer](https://en.wikipedia.org/wiki/Opaque_pointer)として機能するので安全且つコードが自明になります。**










## ネイティブコードからC#のメソッドを呼び出す

例えばネイティブコードからコールバックとしてC#のメソッドを呼び出したいとき、ネイティブプラグインとしては以下の2つのやり方があります。
(公式ドキュメントで言うと[こちらのページにある「Calling C# back from native code」で解説されている内容](https://docs.unity3d.com/Manual/PluginsForIOS.html))

- `UnitySendMessage`経由で呼び出し
- ネイティブコードにdelegateを渡して呼び出してもらう

**この章では後者の「ネイティブコードにdelegateを渡して呼び出してもらう」やり方を中心に解説していきます。**
`UnitySendMessage`は補足として後述します。

主に以下のポイントを理解するのが目的です。

- `ネイティブコードに渡すデリゲート(C#)`と`対応する関数ポインタ(ObjC++)`の関連性
    - iOSビルド(IL2CPP時)に於ける制約
- ネイティブコード側で渡されたコールバックを保持する方法 + 呼び出し方について


### この章のサンプルプロジェクト

サンプルプロジェクトは以下のリポジトリの`objCpp/callbackExample`ブランチにあります。

- [mao-test-h/UnityNativePlugin-for-iOS-Examples - objCpp/callbackExample](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/tree/objCpp/callbackExample)

こちらも基本的な構成は[最小構成サンプル](#最小構成から見るネイティブプラグイン基礎)とさほど変わりはありませんが、シーン中には登録したコールバックを呼び出すための`Callボタン`を配置してます。

ちなみに今回のサンプルではただコールバックを渡して呼び出すだけではなく、**渡したコールバックをネイティブクラスのメンバ変数として保持し、インスタンスメソッド経由で呼び出すような実装にしてます。** 



### 実装のポイント

ソース中から必要な箇所を掻い摘んで解説していきます。
全ソースコードについては以下を参照。

- [objCpp/callbackExample - Example.mm](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/callbackExample/Assets/CallbackExample/Plugins/iOS/Example.mm)
- [objCpp/callbackExample - Example.cs](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples/blob/objCpp/callbackExample/Assets/CallbackExample/Scripts/Example.cs)


#### ■ `ネイティブコードに渡すデリゲート`と`対応する関数ポインタ`の定義

先ずC#側では**ネイティブコードに渡したいコールバックのデリゲート型**を定義します。
→ 以下のコードで言う`delegate void SampleCallbackDelegate(Int32 num);`が該当

```csharp:Example.cs
// 登録するメソッド(ここで言う`SampleCallback`)と同じフォーマットのデリゲート
// NOTE: ネイティブコード側で定義している以下の関数ポインタに対応する
// > typedef void (* sampleCallbackDelegate)(int32_t);
delegate void SampleCallbackDelegate(Int32 num);
```

それと合わせて**ネイティブコード側にも同じフォーマットの関数ポインタ**を定義します。
→ 以下のコードで言う`typedef void (* sampleCallbackDelegate)(int32_t);`が該当

コード上では便宜的に(キャメルケースこそ違えど)名前を同名にしてますが、フォーマットさえ合っていれば別名でも問題ないです。

```obj-c++:Example.mm
// MARK:- 関数ポインタ

// NOTE: C#側で定義している以下のデリゲート型に対応する関数ポインタ
// > delegate void SampleCallbackDelegate(Int32 num);
typedef void (* sampleCallbackDelegate)(int32_t);
```


#### ■ C#側で「実際にネイティブコードからコールバックとして呼び出されるメソッド」を定義

C#側では**実際にネイティブコードからコールバックとして呼び出されるメソッド**を定義します。
→ 以下のコードで言う`SampleCallback(Int32 num)`が該当

ポイントとしては以下です。

- iOS(と言うよりはIL2CPP)の場合には、**staticメソッド且つ`AOT.MonoPInvokeCallbackAttribute`属性を付ける必要がある**
- `AOT.MonoPInvokeCallbackAttribute`の引数には**ネイティブコードに渡したいコールバックのデリゲート型**のTypeを渡す
    - 今回で言うと`SampleCallbackDelegate`が該当

```csharp:Example.cs
// 実際にネイティブコードから呼び出されるメソッド
// NOTE: iOS(正確に言うとAOT)の場合には「staticメソッドな上で`MonoPInvokeCallbackAttribute`を付ける必要がある」
[AOT.MonoPInvokeCallbackAttribute(typeof(SampleCallbackDelegate))]
static void SampleCallback(Int32 num)
{
    Debug.Log($"ネイティブコードから呼び出された : {num}");
}
```

##### 【補足】 JITだと上記の制約は無い

今回はiOSビルドを対象としているので、IL2CPPによるAOTコンパイルがほぼ強制となるために上記の制約が付きますが、例えばStandaloneと言ったMonoバックエンドが許される環境に於いては上記制約が無く、普通にコールバックにラムダ式を渡すことが出来たりします。

因みにこの制約はXamarinも同様な模様。

- [Xamarin iOSのP/Invokeでコールバックを使うときの制限](http://espresso3389.hatenablog.com/entry/2013/06/01/215646)



#### ■ ネイティブコードにコールバックを渡す

C#側にて`RegisterSampleCallback`メソッドからネイティブコード側に`SampleCallback(int num)`を登録します。
(この例では`Start()`内で登録)

```csharp:Example.cs
        IntPtr _instance = IntPtr.Zero;

        void Start()
        {

#if !UNITY_EDITOR && UNITY_IOS
            _instance = CreateExample();
            // インスタンスにコールバックを登録しておく
            RegisterSampleCallback(_instance, SampleCallback);
#endif
        }

        #region P/Invok

        // `registerSampleCallback`の呼び出し
        [DllImport("__Internal", EntryPoint = "registerSampleCallback")]
        static extern void RegisterSampleCallback(IntPtr instance, SampleCallbackDelegate callback);

        #endregion P/Invoke

        #region P/Invoke Callback

        // 登録するメソッド(ここで言う`SampleCallback`)と同じフォーマットのデリゲート
        // NOTE: ネイティブコード側で定義している以下の関数ポインタに対応する
        // > typedef void (* sampleCallbackDelegate)(int32_t);
        delegate void SampleCallbackDelegate(Int32 num);


        // 実際にネイティブコードから呼び出されるメソッド
        // NOTE: iOS(正確に言うとAOT)の場合には「staticメソッドな上で`MonoPInvokeCallbackAttribute`を付ける必要がある」
        [AOT.MonoPInvokeCallbackAttribute(typeof(SampleCallbackDelegate))]
        static void SampleCallback(Int32 num)
        {
            Debug.Log($"ネイティブコードから呼び出された : {num}");
        }

        #endregion P/Invoke Callback
```

ネイティブコード側では`Example`クラスのメンバ変数に関数ポインタを持たせることで、**C#から渡されるコールバックをメンバ変数として保持できるようにしておきます。**

```obj-c++:Example.mm
@implementation Example {

    // メンバ変数
    // NOTE: `registerSampleCallback`から渡されるC#のコールバックを持つ
    sampleCallbackDelegate _sampleCallbackDelegate;
}

// コールバックの登録
- (void)registerSampleCallback:(sampleCallbackDelegate)delegate {
    _sampleCallbackDelegate = delegate;
}

@end
```

あとは外部宣言関数である`registerSampleCallback`内にて、`Example`クラスが持つ登録関数にC#から渡ってくるコールバックを渡してやります。

※`NOTE`とアノテーションが付いたコメントにもありますが、渡されたコールバックはこの時点で呼び出すことも可能です。

```obj-c++:Example.mm
#ifdef __cplusplus
extern "C" {
#endif

// C#から渡されるコールバックをインスタンスに登録
void registerSampleCallback(Example* instance, sampleCallbackDelegate delegate) {

    // NOTE: こういう感じに直接呼び出すこともできる
    //delegate(1);

    // インスタンスに登録
    [instance registerSampleCallback:delegate];
}

#ifdef __cplusplus
}
#endif
```

#### ■ ネイティブコードからコールバックを呼び出す

検証用に「ネイティブコード側で保持してあるコールバック」を呼び出せる仕組みを実装していきます。

ネイティブコード側の`Example`クラスには**メンバ変数に保持しているコールバックを呼び出せる関数**を実装しておき、合わせて`P/Invoke`用の外部宣言関数の方も用意しておきます。

```obj-c++:Example.mm
@implementation Example {

    // メンバ変数
    // NOTE: `registerSampleCallback`から渡されるC#のコールバックを持つ
    sampleCallbackDelegate _sampleCallbackDelegate;
}

// `_sampleCallbackDelegate`に登録してあるコールバックを呼び出す
- (void)callSampleCallback {
    // コールバックが登録されているなら呼び出し
    if (_sampleCallbackDelegate != NULL) {
        _sampleCallbackDelegate(2);
    }
}

@end

#ifdef __cplusplus
extern "C" {
#endif

// インスタンスに登録したコールバックを呼び出し
void callSampleCallback(Example* instance) {
    [instance callSampleCallback];
}

#ifdef __cplusplus
}
#endif

```

あとはC#から外部宣言関数である`callSampleCallback`を`P/Invoke`で呼び出すことで、ネイティブコード側で保持されている`SampleCallback`が呼び出されます。

今回の例だと即時的にコールバックが呼び出されますが、**例えばこちらの方法を用いることでネイティブコード側で非同期な処理を書いてコールバックで値を返すと言ったことが出来るようになります。**

```csharp:Example.cs
        IntPtr _instance = IntPtr.Zero;

        void Start()
        {
            _buttonCall.onClick.AddListener(() =>
            {
#if !UNITY_EDITOR && UNITY_IOS
                // ネイティブプラグインの呼び出し
                CallSampleCallback(_instance);
#else
                // それ以外のプラットフォームからの呼び出し (Editor含む)
                Debug.Log("iOS以外からの呼び出し");
#endif
            });
        }

        #region P/Invok

        // `callSampleCallback`の呼び出し
        [DllImport("__Internal", EntryPoint = "callSampleCallback")]
        static extern void CallSampleCallback(IntPtr instance);

        #endregion P/Invoke

        #region P/Invoke Callback

        // 実際にネイティブコードから呼び出されるメソッド
        // NOTE: iOS(正確に言うとAOT)の場合には「staticメソッドな上で`MonoPInvokeCallbackAttribute`を付ける必要がある」
        [AOT.MonoPInvokeCallbackAttribute(typeof(SampleCallbackDelegate))]
        static void SampleCallback(Int32 num)
        {
            Debug.Log($"ネイティブコードから呼び出された : {num}");
        }

        #endregion P/Invoke Callback
```


### 【補足】 `UnitySendMessage`について

放ったらかしにするのもアレなので補足として軽く解説しておくと、前者で挙げている`UnitySendMessage`は手軽な反面、以下のような制約があります。

- シーン中に決まった名前のGameObject + 決まった名前のメソッドが存在すること 
- **呼び出すメソッドの引数に文字列しか渡せない**
- 呼び出しに1フレの遅延が発生する

やりたいことが↑の制約内で収まる範疇内ならまだしも、これだけだと厳しいときも出てくるかもしれないので、今回はもう一つの手法であるネイティブコードにdelegateを渡す手法の方を主にして解説させてもらいました。

`UnitySendMessage`の呼び出し方については以下の記事にて実装例が載っているので、こちらを御覧ください。

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










# その他 補足集

別記事に分けるほどでもない幾つかの補足を纏めます。


## P/Invokeについて

P/Invokeを簡単に説明すると **「ネイティブコードをC#のメソッドのように呼び出すための機構」** です。

- **参考リンク**
    - [プラットフォーム呼び出し](https://ufcpp.net/study/csharp/sp_pinvoke.html#c-style-function)
    - [Unityネイティブプラグインマニアクス](https://www.slideshare.net/dena_tech/unity-denatechcon)


### どの様な場面で使うのか？

こちらを利用することで、例えば **「Unity C#だけでは呼び出すことが出来ないプラットフォーム固有の機能」** をC#上から呼び出せるようになります。

例を挙げると以下の記事にある **「Unity上からiOS端末の発熱状態を取得する」** と言った機能を実装することが出来るようになります。

- [【Unity】Unity上からiOS端末の発熱状態を取得する](https://qiita.com/mao_/items/af5af5c05293e81f7ca8)


### 実装のポイント

C#からiOSのネイティブコード(`ObjC++`)を呼び出す上でのポイントを幾つか挙げます。

- P/Invokeで呼び出すメソッドには`extern`修飾子を付ける
- その上でiOS向けのネイティブプラグインの場合には`[DllImport("__Internal")]`属性を付ける[^3]
- `DllImport`属性の`EntryPoint`には「ネイティブコード側で外部宣言しているメソッド名」を入れる
    - →今回で言うと`printHelloWorld`

[^3]: ここらの指定はプラットフォームによって変わるので、全プラットフォームこれで行けるワケでは無いことだけ一応補足

<details><summary><b>※ 呼び出す対象のネイティブコード(ObjC++)はこちら (クリックで展開)</b></summary><div>

```obj_c++:Example.mm
// MARK:- extern "C" (Cリンケージで宣言)

#ifdef __cplusplus
extern "C" {
#endif

// NOTE: この関数が実際にUnity(C#)から呼び出される
int32_t printHelloWorld() {

    // 上記で宣言・実装した`Example.printHelloWorld`を呼び出す。呼び出し時の構文はObjC形式となる。
    // NOTE: クラスメソッド(静的関数)として実装しているので、クラスをインスタンス化せずに直接呼び出せる
    return [Example printHelloWorld];
}

#ifdef __cplusplus
}
#endif
```

---

</div></details>


```csharp:Example.cs
// ObjectiveC++コードで実装した`Example`クラスのP/Invoke

/// <summary>
/// `printHelloWorld`の呼び出し
/// </summary>
/// <remarks>
/// NOTE: Example.mmの「extern "C"」内で宣言した関数をここで呼び出す
/// - iOSのネイティブプラグインは静的に実行ファイルにリンクされるので、`DllImport`にはライブラリ名として「__Internal」を指定する必要がある
/// - `EntryPoint`に.mm側で宣言されている名前を渡すことでC#側のメソッド名は別名を指定可能
/// </remarks>
[DllImport("__Internal", EntryPoint = "printHelloWorld")]
static extern Int32 PrintHelloWorld();
```

特に３つ目の`EntryPoint`についてはあまり実装されている記事を見受けない(?)ものの、覚えておくとネイティブコード側とC#側で命名規則を分けることが出来るので便利です。

ちなみに`EntryPoint`を指定しない場合には、以下のように**ネイティブコード側で外部宣言している関数名と合わせる**ことで呼び出すことも可能です。(全体的にこの書き方をよく見受ける印象)

```csharp:Example.cs
// NOTE: ちなみに`EntryPoint`を指定しない場合は、以下のようにC#側のメソッド名を.mmで外部宣言している関数名と同名に合わせる必要がある
[DllImport("__Internal")]
static extern Int32 printHelloWorld();
```





## ヘッダーファイルは必要？

今回のサンプルでは意図的にヘッダーファイル(`.h`)に分けていません。
理由としては**実装したクラスが他のネイティブコードから参照されない前提があるから**と言うのが挙げられます。

これはあくまで**私個人のやり方**にはなりますが、基本的に作成するネイティブプラグインが機能単位で独立している上で、他のソースからも参照されない場合には`.mm`の1ソースだけで完結させてしまうことが多いです。(この方が管理が楽)

逆に他のソースからも参照する必要が出てきたタイミングで「ヘッダーに分離しても良いかな？」と言う感じで実装してます。

**上述の通り、私個人のやり方なのでチームやプロジェクトごとにルールが決まっている場合にはそれに準拠させることをオススメします。**

### サンプルコード

[最小構成サンプル](#最小構成から見るネイティブプラグイン基礎)の`Example.mm`を分けると以下のようになります。

<details><summary><b>Example.mm (クリックで展開)</b></summary><div>

```obj-c++:Example.mm
#import "Example.h"

// MARK:- implementation (クラスの実装部)

@implementation Example

+ (int32_t)printHelloWorld {
    // ログ出力
    NSLog(@"Hello World");

    // 戻り値を返す
    return 2;
}

@end
```
</div></details>

<details><summary><b>Example.h (クリックで展開)</b></summary><div>

```obj-c++:Example.h
#import <Foundation/Foundation.h>

#ifndef Example_h
#define Example_h

// MARK:- interface (クラスの宣言部)

@interface Example : NSObject

/// ログに"Hello World"と出力して2を返す。
/// NOTE: ここではクラスメソッド(静的関数)として実装
+ (int32_t)printHelloWorld;

@end


// MARK:- extern "C" (Cリンケージで宣言)

#ifdef __cplusplus
extern "C" {
#endif

// NOTE: この関数が実際にUnity(C#)から呼び出される
int32_t printHelloWorld() {

    // 上記で宣言・実装した`Example.printHelloWorld`を呼び出す。呼び出し時の構文はObjC形式となる。
    // NOTE: クラスメソッド(静的関数)として実装しているので、クラスをインスタンス化せずに直接呼び出せる
    return [Example printHelloWorld];
}

#ifdef __cplusplus
}
#endif


#endif /* Example_h */
```
</div></details>





## `.m(ObjC)`だけでは実装できないのか？ → `.mm(ObjC++)`を使わない例

「今回のサンプルのソースは`.mm`で実装されているが、`.m`だけで実装できないのか？」と言う話についてですが、こちらは出来ます。
**その際には拡張子を`.m`にした上で、外部宣言を以下のように改修する必要があります。**

```obj-c:Example.m
.....

// これだけでC#から呼び出せる
extern int printHelloWorld() {
    return [Example printHelloWorld];
}
```

「何故こうする必要があるのか？」の理由については以下の章で後述してます。

- [逆にObjective-Cならextern "C"は不要](https://qiita.com/mao_/items/ca5af02534c3ca3715c7#%E9%80%86%E3%81%ABobjcm%E3%81%AA%E3%82%89extern-c%E3%81%AF%E4%B8%8D%E8%A6%81)


### `.m`と`.mm`の使い分けについて

あまり詳しくないので持論ベースにはなりますが...**私個人としては以下の理由から`.mm`にする事が多いです。**

- C++の資産(ライブラリとか)を利用することがある
- **そもそも`.m`で出来ることは`.mm`でも出来るという認識なので、特筆して前者にする理由がない**

ここらの裁量については「これが正しい」とは言い切れないかと思われるので、とりあえずは仕様を理解した上で使い分けていくと良いかもしれません。
(寧ろ敢えて`.m`にする利点があったら教えて頂けると幸いです。。:bow:)





## ネイティブコード側のインスタンスの生成/解放時の処理についての補足

インスタンスの生成/解放処理に注目すると、対象のインスタンスに対して`CFRetain`/`CFRelease`と言う処理を呼び出してます。

ここでやっていることをザックリと説明すると、**Objective-Cの`ARC(Automatic Reference Counting):自動参照カウント`を無効化して自前で`Retain/Release`を呼び出して参照カウントを管理するようにしてます。**

元に生成したインスタンスのポインタはそのままC#側に渡されて管理される想定があるので、`ARC`と言った機能は無効化しておく必要があります。

- **参考リンク**
    - [[ARC対応] Bridgeキャストの使い方](https://fernweh.jp/b/arc-__bridge/)

```obj-c++:Example.mm
// インスタンス化
// NOTE: 戻り値のポインタをC#側でIntPtrなどで保持し、インスタンスメソッドの呼び出し時に渡して使う
Example* createExample() {
    Example* instance = [[Example alloc] init];
    CFRetain((CFTypeRef) instance);
    return instance;
}

// 解放
void releaseExample(Example* instance) {
    CFRelease((CFTypeRef) instance);
}
```






## NSObjectについて

ObjCに於いて殆どのクラスの基底となるクラスであり、「メモリ領域の確保(`alloc`)」や「インスタンスの初期化(`init`)」と言ったオブジェクトの基本となる機能が実装されてます。

- [NSObject](https://developer.apple.com/documentation/objectivec/nsobject?language=objc)
- [NSObjectクラス - macOS/iOS API解説](https://cocoaapi.hatenablog.com/entry/10010214/NSObject)

ObjCで実装するクラスについては、基本的には`NSObject`を継承したサブクラスを実装することになります。



### プレフィックスの`NS`の意味

iOS関連のコードを追っていると`NSObject`以外にも`NSArray`や`NSData`とプレフィックスが「`NS`」から始まるクラスが多数出てきます。

そこで「この"NS"って何やねん？」と思って軽く調べてみたところ、この頭文字は **`NeXTSTEP`と呼ばれるmacOSやiOSの前身となるOSの略称であり、そこから来ているみたいです。**
簡単な話、 **歴史的な経緯で付いてしまった名称かと思われるので**そこまで深く考えなくても良さそうです。
詳細については以下の記事の「Objective-Cの歴史」に詳しく纏まってます。

- [Objective-C 入門 - Objective-Cの歴史](http://vivacocoa.jp/objective-c3e/chapter1a.html)





## `ObjC/ObjC++`と`Swift`のどちらで実装すればよいか？

**追記:** [Swiftで実装しないのか？](#swiftで実装しないのか)の章でも記載したとおりに、**Swiftだけでプラグインの実装を完結できることが分かりました。**

その為、少なくとも自分の中では大分状況が変わりました。
更に言うと**本編中のサンプルコードは全てSwiftで置きかえが可能です。** (詳細は以下の追記記事に記載)

- [サンプルコードをSwiftだけで完結出来るように置き換える](https://qiita.com/mao_/items/07466e169d08cbeff221)

個人的には可読性や保守性などを考慮してSwiftを選定したいところですが...実際のところは既存の資産やスキルセットに合わせて選定するのが良いのかもしれません。


<details><summary>この章の過去の内容はこちら (クリックで展開)</summary><div>

この内容はほぼほぼ持論と言いますか、 **「あくまで自分ならこうする」** と言った判断基準でしかないので、それを踏まえた上での一例として捉えていただけると幸いです。

私は主に以下の基準で選定することが多いです。

### `ObjC/ObjC++`を選定するとき

- **簡単なネイティブプラグインを実装するとき**
    - 例えば外部宣言関数だけで完結できるような簡単なAPIの呼び出しとか
    - ソースファイルが1つで済むために管理しやすい
- **汎用的なライブラリを実装するとき**
    - ライブラリ側の都合で`[PostProcessBuild]`をフックして`PBXProject`を書き換えたくないモチベがある (Swiftバージョンの指定とか)
        - 導入先で`PBXProject`の変更内容が競合する可能性が無きにしもあらず
    - [この記事](https://qiita.com/mao_/items/2305822054dadb2c5da8)にある通り、Unity2019.3前後で大きく構成が変わるので、広いバージョンの保守を踏まえるとSwiftの準拠が面倒
        - ※逆に「2019.3以降のみ対応」と割り切ったり、数年経ってUnity2019.3よりも前のバージョンが完全に過去のものになった + 今の構成から大きく変わっていないなら忘れても良いかも
- **C++のライブラリを利用したり、ポインタ操作などを行うとき**
    - ※一応Swiftにもポインタ型があったりとポインタに関する操作はできるが、個人的にはこのパターンだとC++で書いた方が楽


### `Swift`を選定するとき

- [Unity2019.3前後の変更点](https://qiita.com/mao_/items/2305822054dadb2c5da8)を許容できる上で、保守的にSwiftで書きたいとき

繰り返しにはなりますが、「あくまで自分ならこうする」と言った判断基準のため、実際には各々の要件や技術スタックに合わせて選定することをおすすめします。

---

</div></details>





# 参考/関連リンク

- **Unity - Documantation**
    - [Building plug-ins for iOS](https://docs.unity3d.com/Manual/PluginsForIOS.html)
    - [Structure of a Unity Xcode Project](https://docs.unity3d.com/Manual/StructureOfXcodeProject.html)
    - [iOS build settings](https://docs.unity3d.com/Manual/BuildSettingsiOS.html)
- **iOSネイティブプラグイン関連**
    - [Unityネイティブプラグインマニアクス](https://www.slideshare.net/dena_tech/unity-denatechcon)
    - [[Unity] C#とObjective-Cの連携まとめ](https://qiita.com/tkyaji/items/74d485a021c75ed10bca)
    - [[Swift] SwiftとObjective-Cで相互に連携する](https://qiita.com/edo_m18/items/861d090a5471f4f0eeae)
    - [UnityでXcodeの設定を自動化する方法まとめ](https://smartgames.hatenablog.com/entry/2016/06/19/164052)
    - [Unity で iOS連携](https://note.com/attomicgm/n/n89d80be0e75a)
    - [iOSのビルド設定の「Symlink Unity libraries」の意味](https://nakamura001.hatenablog.com/entry/20120906/1346937762)
    - [【Unity】iOSネイティブプラグインをSwiftで実装する際には、2019.3前後で設定方法が変わる](https://qiita.com/mao_/items/2305822054dadb2c5da8)
    - [【Unity】Unity上からiOS端末の発熱状態を取得する](https://qiita.com/mao_/items/af5af5c05293e81f7ca8)
    - [Swift だけで Unity の iOS の Native Plugin を作る](https://qiita.com/fuziki/items/955c2b35514bcfc37969)
    - [[Unity]Swift だけで作ったNative PluginでUnityにコールバック](https://qiita.com/fuziki/items/b51353e9fe8f59640cc3)
- **C#**
    - [プラットフォーム呼び出し](https://ufcpp.net/study/csharp/sp_pinvoke.html)
    - [Xamarin iOSのP/Invokeでコールバックを使うときの制限](http://espresso3389.hatenablog.com/entry/2013/06/01/215646)
- **iOSネイティブ**
    - [Apple Developer Documantation](https://developer.apple.com/)
    - **Objective-C/C++**
        - [Objective-C - Wikipedia](https://ja.wikipedia.org/wiki/Objective-C)
        - [Objective-Cのクラス定義を理解しよう](https://www.atmarkit.co.jp/ait/articles/0811/10/news108.html)
        - [Objective-C 入門](http://vivacocoa.jp/objective-c3e/chapter3c.html)
        - [Objective-C++ 取扱説明書 〜始め方から使い方、注意点まで〜【完全保存版】](https://marycore.jp/prog/objective-c/objcpp-basics/)
        - [[ARC対応] Bridgeキャストの使い方](https://fernweh.jp/b/arc-__bridge/)
        - [Objective-C 供養](https://dev.to/szktty/objective-c-59gm)
        - [なぜObjective-Cの拡張子が”m”なのか？](https://www.mindto01s.com/2013/01/13/why_objc_extension_is_m.html)
        - [Why do Objective-C files use the .m extension?](https://stackoverflow.com/questions/652186/why-do-objective-c-files-use-the-m-extension)
    - **Swift**
        - [[Swift]アクセス修飾子](https://qiita.com/GATA/items/90341f56a56ff584e756)
    - **API**
        - [NSObject - Apple Developer Documantation](https://developer.apple.com/documentation/objectivec/nsobject?language=objc)
        - [NSObjectクラス](https://cocoaapi.hatenablog.com/entry/10010214/NSObject)
    - **Xcode**
        - [Xcode Build Settings](https://xcodebuildsettings.com/)
- **C/C++**
    - [CとC++が混在したプログラムでの注意点](https://qiita.com/nomunomu0504/items/722a2771fef7d8038ceb)
    - [C++のマングルとextern "C" {](https://www.wagavulin.jp/entry/2017/02/09/215036)
    - [言語リンケージ](https://docs.oracle.com/cd/E19957-01/805-7888/z4000220f34c0/index.html)
    - [C言語 extern宣言とグローバル変数【宣言不要な設計手法】](https://monozukuri-c.com/langc-funclist-extern/)
    - [名前マングリング (C++ のみ)](https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_72/rzarg/name_mangling.htm)

## 追記記事

- [サンプルコードをSwiftだけで完結出来るように置き換える](https://qiita.com/mao_/items/07466e169d08cbeff221)

## 補足記事

- [Objective-C++について](https://qiita.com/mao_/items/ca5af02534c3ca3715c7)
- [ネイティブプラグインの設計について](https://qiita.com/mao_/items/15d05d25a99ab290fa50)
- [ネイティブプラグインを実装するには](https://qiita.com/mao_/items/88aeac26adb17194ebdc)
- [Xcodeの設定を自動化する](https://qiita.com/mao_/items/c678f93ee04608492788)

## サンプル

- [mao-test-h/UnityNativePlugin-for-iOS-Examples](https://github.com/mao-test-h/UnityNativePlugin-for-iOS-Examples)
- [Unity上からiOS端末の発熱状態を取得する](https://gist.github.com/mao-test-h/f7a6733fc3eea2b73f617717c82d0351)
- [Unity上からiOS端末のダークモード判定を行う](https://gist.github.com/mao-test-h/b1cea20820832499a7e7942589db7e48)
- [Unity上からiOS端末のシステムボリュームを取得する](https://gist.github.com/mao-test-h/f2711376037dd77fcfa6e835fc2268eb)
- [Unity上からiOS端末の画面輝度を取得/設定](https://gist.github.com/mao-test-h/95b8de9147c95a58570683ce67b864de)





# 謝辞

いつも配信で私を元気付けてくれるバーチャルタレントの **[巻乃もなか](https://twitter.com/monaka_0_0_7?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor)** さん[^1]に深く感謝を申し上げます。
配信に元気付けられてこの記事を書ききることが出来ました！

[^1]: 私の最推しのバーチャルタレント(所謂VTuber)さんです！