UnityのiOSネイティブプラグインをSwiftで書くためのネイティブプラグイン


UnityのiOSネイティブプラグインはObjective-Cで記述して、Objective-CからUnity(C#)側のメソッド呼び出しは`UnitySendMessage`関数、UnityからObjective-C側のメソッド呼び出しは`extern "C"`で宣言されたCインターフェイスを通しておこなうのがセオリーとなっています。

近頃Swiftネイティブの開発者も増えてきたということもあり、タイトルの通り「UnityのiOSネイティブプラグインをSwiftで書くためのネイティブプラグイン」を作ってみました。

## SwiftからのUnity側のメソッドを呼び出す

SwiftからCの関数やObjective-Cのクラスにアクセスするには、それらが宣言されているヘッダーファイルを、特定のヘッダーファイル（Bridging Header）でimportしておきます。
Bridging Headerは、Xcodeで新規プロジェクトを作成する際にLanguageにSwiftを選択したプロジェクトでObjective-Cのファイルを作成するとき、あるいは逆にObjective-CのプロジェクトでSwiftのファイルを作成するときに、自動的にこれを作成するかどうかを聞かれます。このときのデフォルトのファイル名は`<Product Name>-Swift-Bridge.h`ですが、これはBuild Settings/Swift Compiler - Code Generationの`Objective-C Bridging Header`（`SWIFT_OBJC_BRIDGING_HEADER`）の設定で変更できます。

Objective-CからUnity側のメソッドを呼び出す`UnitySendMessage`関数やUnityシーンのルートビューコントローラーを取得する`UnityGetGLViewController`関数は、Unityプロジェクトをビルドして生成されたXcodeプロジェクトに含まれている`UnityInterface.h`で宣言されているので、これをBridging Headerでimportします。
また、`UnityInterface.h`では、`NSString`や`UIView`といったクラスを参照しているので、`Foundation.h`や`UIKit.h`もimportしておきます。

```UnitySwift-Bridging-Header.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"
```

これだけでCの関数をSwiftから直接呼び出せるようになり、`UnitySendMessage`関数を使ってUnity側のメソッドを呼び出すことができます。

```Example.swift
import Foundation

class Example : NSObject {
    static func callUnityMethod() {
        // Call a method on a specified GameObject.
        UnitySendMessage("CallbackTarget", "OnCallFromSwift", "Hello, Unity!")
    }
}

```

## UnityからSwift側のメソッドを呼び出す

UnityからSwiftのクラスにアクセスする方法は、従来のUnityからObjective-Cのクラスにアクセスする方法とほぼ同様です。

### ステップ 1: Swiftのクラスを作成する

```Example.swift
import Foundation

class Example : NSObject {
    static func swiftMethod(_ message: String) {
        print("\(#function) is called with message: \(message)")
    }
}
```

### ステップ 2: .mm（Objective-C++）ファイルを作成して、extern "C"宣言でCインターフェイスを作成する

```Example.mm
#import <Foundation/Foundation.h>
#import "unityswift-Swift.h"    // Required

extern "C" {
    void _ex_callSwiftMethod(const char *message) {
        // You can access Swift classes directly here.
        [Example swiftMethod:[NSString stringWithUTF8String:message]];
    }
}
```

このとき、`<Product Module Name>-Swift.h`という名前のヘッダーファイル（上の例では`unityswift-Swift.h`）をimportすることで、SwiftのクラスにObjective-C++からアクセスできます。
このヘッダーファイルは、Xcodeでビルドを実行するとDerivedDataの下に自動的に生成されます。また、ファイル名はBuild Settings/Swift Compiler - Code Generationの `Objective-C Generated Interface Header Name`（`SWIFT_OBJC_INTERFACE_HEADER_NAME `）の設定で変更できます。

### ステップ 3: C#側のインターフェイスを作成する

ステップ 2で`extern "C"`で宣言したCインターフェイスは、C#側で`[DllImport("__Internal")]`属性を付けることでアクセス可能になります。

```Example.cs
using System.Runtime.InteropServices;

public class Example {
    #if UNITY_IOS && !UNITY_EDITOR
    [DllImport("__Internal")]
    private static extern void _ex_callSwiftMethod(string message);
    #endif

    public static void CallSwiftMethod(string message) {
        #if UNITY_IOS && !UNITY_EDITOR
        _ex_callSwiftMethod(message);
        #endif
    }
}
```

これで、C#のクラスから以下のような記述でSwift側のメソッドを呼び出せます。

```
Example.CallSwiftMethod("Hello, Swift!");
```

従来のObjective-Cで記述したネイティブプラグインでは、ステップ 1のクラス定義とステップ 2のインターフェイス宣言を1つの.mmファイルに記述していました。
インターフェイス宣言はそのままに、クラス定義部分をSwiftに移植することで、従来と同様にC#からアクセスできます。

## Runpath Search Pathsの設定

ここまででSwift-C#間で相互にメソッドの呼び出しをおこなえるようになりましたが、実行時に次のようなエラーが発生します。

```Console
dyld: Library not loaded: @rpath/libswiftCore.dylib
```

これは、Unityプロジェクトをビルドして生成されたXcodeプロジェクトではBuild Settings/Linkingの`Runpath Search Paths`（`LD_RUNPATH_SEARCH_PATHS`）が設定されていないためで、`@executable_path/Frameworks`に設定することで回避できます。

## ネイティブプラグイン化

Bridging HeaderやObjective-C Generated Interface Header Nameは、デフォルトではProduct Nameに依存するためプロジェクトによって毎回変わってしまうし、毎回`Runpath Search Paths`を手動で設定するのも面倒なので、これをネイティブプラグイン化しました。

[miyabi/unity-swift: Native plugin to write native code in Swift for Unity.](https://github.com/miyabi/unity-swift)

このプラグインでは、以下の設定をUnityプロジェクトのビルド時に自動的におこないます。

-	`Objective-C Bridging Header`を`UnitySwift-Bridging-Header.h`に設定し、あらかじめ`Foundation.h`、`UIKit.h`、`UnityInterface.h`をimportします。
-	`Objective-C Generated Interface Header Name`を`unityswift-Swift.h`に設定します。
-	`Runpath Search Paths`を`@executable_path/Frameworks`に設定します。

unity-swiftを使って実際にSwiftでネイティブプラグインを作成した例は、[Unity ReplayKit Bridge](https://github.com/miyabi/unity-replay-kit-bridge)のswiftブランチを参照してください。

[unity-replay-kit-bridge/Example/Assets/UnityReplayKitBridge at swift · miyabi/unity-replay-kit-bridge](https://github.com/miyabi/unity-replay-kit-bridge/tree/swift/Example/Assets/UnityReplayKitBridge)