LoginSignup
31
27

More than 3 years have passed since last update.

【Unity】iOSネイティブプラグインをSwiftで実装する際には、2019.3前後で設定方法が変わる

Last updated at Posted at 2021-01-03

Unity 2019.3から新機能としてUnity as a Libraryが入った影響で、iOS向けビルド後の.xcodeprojの構成に大きく変更が掛かりました。

その影響としてか、例えば以下の記事にある方法でSwiftコードを実装しようとしてもビルドが通らない状況となっていたので、今回は従来の方法と照らし合わせつつ解決策諸々を備忘録序に纏めていければと思います。
※以降、以下に記事にある2019.2までのやり方を従来のやり方と表記する形で解説していきます。

ひょっとしたら「実はこのパターンが出来なかった」といった見落としがあるかもしれないので、そちらについては分かり次第随時追記予定。
(もし「ここの内容だとあれが出来ない」と言った情報があれば、コメントや編集リクエストなどで教えて頂けると幸いです :pray: :sweat_drops: )

バージョン

  • Unity
    • 2018.4.30f1
      • 従来のやり方の検証に利用
    • 2019.4.17f1
      • 2019.3からのやり方の検証に利用
  • Xcode 12.3

公開リポジトリ

今回検証に用いたプロジェクト一式をGitHubにて公開してます。
「従来のやり方を適用したプロジェクト」と「2019.3からのやり方を適用したプロジェクト」の2つを用意してます。

2019.3からどの様な変更が掛かったのか?

※一応補足として記載しておきます。「ここまでは知ってるので、早くSwiftコードを実装する方法を知りたい」という方は読み飛ばして下さい。

従来はUnity-iPhoneと言うターゲットに全てのコードやデータなどが集約される形となっておりましたが、2019.3からは新たに「UnityFramework」と言うフレームワークのターゲットが追加されており、ClassesLibraries folders、その他依存関係となっているフレームワーク各種はこちらに切り離されるようになりました。

ここらの詳細については公式ドキュメントの方にも記述があるので、詳しくは以下を御覧ください。

この変更によって大きく影響を受ける箇所としては、恐らくは[PostProcessBuild]で処理されるであろうPBXProjectの更新処理であり、GUIDの向き先を以下の様にUnityFrameworkに向け直す必要が出てきました。

        [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));

#if UNITY_2019_3_OR_NEWER
            // NOTE: 2019.3からは`UnityFramework`に設定を適用
            var targetGUID = project.GetUnityFrameworkTargetGuid();
#else
            var targetName = PBXProject.GetUnityTargetName();
            var targetGUID = project.TargetGuidByName(targetName);
#endif

            project.AddFrameworkToProject(targetGUID, "MonafuwaLowMemoryWarningFramework.framework", false);
            File.WriteAllText(projectPath, project.WriteToString());
        }

ただ、Swiftを扱う上では↑の様に単純にGUIDの向き先をUintyFrameworkに変えるだけでは解決しないので、具体的な解決策を後述していきます。

従来のSwiftコードの実装方法

先に予備知識として2019.2までの従来の実装方法について記載していきます。

とは言え、やっている事自体はほぼ以下の記事にあるやり方そのままです。
こちらも大体把握されている方は読み飛ばしてしまっても問題有りません。

ここでは以下のプロジェクトにあるExsamplesを元に解説していきます。

PBXProjectの変更点

ほぼ記事にあるやり方のとおりです。
幾つか要点だけ箇条書きで纏めておきます。

  • Objective-C Bridging HeaderUnitySwift-Bridging-Header.hを設定
  • Objective-C Generated Interface Header Nameunityswift-Swift.hを設定
  • Runpath Search Paths@executable_path/Frameworksを設定
  • Swiftのバージョンを5.0に設定
    • ※コメントにも記載しているが、こちらを明示的に指定しないとデフォルトで3.0辺りが設定される?影響で、XcodeによってはUnspecified扱いになることがある
    • ※因みに5.0を指定しているのは最新だからとりあえず指定しているだけであって、特にこれと言った理由などは無い。こちらのバージョンは要件に応じて必要な値を設定すること
XcodePostProcess.cs
    /// <summary>
    /// Swiftを実装するにあたって必要な設定を自動で適用する
    /// </summary>
    sealed class XcodePostProcess
    {
        [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));

            var targetGuid = project.TargetGuidByName(PBXProject.GetUnityTargetName());
            project.SetBuildProperty(targetGuid, "SWIFT_OBJC_BRIDGING_HEADER", "Libraries/UnitySwift/UnitySwift-Bridging-Header.h");
            project.SetBuildProperty(targetGuid, "SWIFT_OBJC_INTERFACE_HEADER_NAME", "unityswift-Swift.h");
            project.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks");

            // Swift version: 5.0
            // NOTE: 明示的に指定しないと3.0ぐらいの古いのが設定されるっぽいので、Xcodeによっては`Unspecified`扱いになる
            project.AddBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");

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

UnitySwift-Bridging-Header.hには予めFoundation.hUIKit.hの他、UnityInterface.hをimportしておきます。
(UnitySendMessageなどはUnityInterface.hで宣言されているため、SwiftからUnitySendMessageを呼び出したい場合にはimportしておく必要がある)

UnitySwift-Bridging-Header.h
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UnityInterface.h"

Swiftのコード

Swiftのコードは以下のように実装してます。

callSwiftMethodはC#側から渡された文字列をprintで出力するだけのものであり、callUnityMethodUnitySendMessageを呼び出してC#側に"Hello, Unity!"と言う文字列を送ります。

Exsample.swift
import Foundation

class Example : NSObject {

    // ObjC++からSwiftのメソッドを呼び出す
    @objc static func callSwiftMethod(_ message: String) {
        print("\(#function) is called with message: \(message)")
    }

    // SwiftのメソッドからSendMessageを呼び出す
    @objc static func callUnityMethod() {
        // Call a method on a specified GameObject.
        UnitySendMessage("CallbackTarget", "OnCallFromSwift", "Hello, Unity!")
    }
}

Objective-C++のコード

SwiftのコードだけだとC#から呼び出せないので、P/Invokeで呼び出せるようにObjC++コードを用意します。
内容はSwift側のメソッドをそのまま呼び出しているだけです。

ExsampleBridge.mm
#import <Foundation/Foundation.h>

// ObjC++からSwiftのクラスにアクセスする際に必要
// NOTE: `unityswift-Swift.h`の実態はビルド時に`DerivedData`以下に自動生成される
#import "unityswift-Swift.h"    // Required

// P/Invoke
extern "C" {

    void callSwiftMethod(const char *message) {
        [Example callSwiftMethod:[NSString stringWithUTF8String:message]];
    }

    void callUnityMethod() {
        [Example callUnityMethod];
    }
}

C#のコード

上記で実装したSwiftコード及びP/Invoke用のObjC++コードの呼び出し処理を実装します。
Exsampleでは画面上にある2つのButtonからSwift側のcallSwiftMethodcallUnityMethodを呼び出せるようにしてます。

なお、callUnityMethodについてはSendMessageで結果を受け取る都合上、GameObject名やメソッド名を合わせる形にして受け取った文字列をログ出力するようにしてます。

Exsample.cs (クリックで展開)
Exsample.cs
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;

namespace UnitySwift.Exsamples
{
    /// <summary>
    /// iOS NativePluginの呼び出しサンプル
    /// </summary>
    public sealed class Exsample : MonoBehaviour
    {
        [SerializeField] Button _callSwiftMethod = default;
        [SerializeField] Button _callUnityMethod = default;

        void Start()
        {
            // SwiftからSendMessageを呼び出す際に指定されているGameObjectの名称を設定
            this.name = "CallbackTarget";

            _callUnityMethod.onClick.AddListener(() =>
            {
#if !UNITY_EDITOR && UNITY_IOS
                CallUnityMethod();
#endif
            });

            _callSwiftMethod.onClick.AddListener(() =>
            {
#if !UNITY_EDITOR && UNITY_IOS
                CallSwiftMethod("Gorilla");
#endif
            });
        }

        /// <summary>
        /// SwiftからSendMessageで呼び出される措定のメソッド
        /// </summary>
        /// <param name="message"></param>
        void OnCallFromSwift(string message)
        {
            Debug.Log(message);
        }

        /// <summary>
        /// ObjC++からSwiftのメソッドを呼び出す
        /// </summary>
        /// <remarks>[C# -> ObjC++ -> Swift]の流れで呼び出される</remarks>>
        [DllImport("__Internal", EntryPoint = "callSwiftMethod")]
        static extern void CallSwiftMethod(string message);

        /// <summary>
        /// SwiftのメソッドからSendMessageを呼び出す
        /// </summary>
        /// <remarks>※SwiftからUnity側で定義されているメソッドを呼び出したい意図がある</remarks>>
        [DllImport("__Internal", EntryPoint = "callUnityMethod")]
        static extern void CallUnityMethod();
    }
}

2019.3からのSwiftコードの実装方法

上述した従来のやり方のまま.xcodeprojをビルドすると確実にエラーが出て怒られます。

その理由の一つとして、UnityFrameworkの実態であるFrameworkと言うターゲットはBridging-Header.hを設定することが出来ないので、恐らくは早いタイミングでこの旨に関連するエラーが出力されるかと思われます。

この他にも細かい所で幾つか変更が掛かっている箇所があるので、順を追って説明していきます。

ここでは以下のプロジェクトにあるExsamplesを元に解説していきます。

PBXProjectの変更点

先にPBXProjectの変更点についての解説ですが、結論から言うと「Swiftのバージョン指定」以外不要になっているように思われました。

UnitySwift-Bridging-Header.hについてはそもそも設定することが出来ないので、プロジェクトからも削除してます。

XcodePostProcess.cs
    sealed class XcodePostProcess
    {
        /// <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();

            // NOTE:
            // 以前まで設定していた`Bridging-Header.h`の設定の類は2019.3からは不要な模様。
            // 寧ろCocoa touch FrameworkがBridging-Headerに対応していないので、設定していると怒られる。

            // Swift version: 5.0
            // NOTE: 明示的に指定しないと3.0ぐらいの古いのが設定されるっぽいので、Xcodeによっては`Unspecified`扱いになる
            project.AddBuildProperty(targetGuid, "SWIFT_VERSION", "5.0");

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

Swiftのコード

変更点としては以下の2点が挙げられます。

  • ObjC++に公開するクラス/メソッドのアクセスレベルをpublicに設定
  • UnitySendMessageの代わりにUnityFrameworkに実装されているsendMessageToGOを利用

後者の変更については以下のissue/PRを参考にさせて頂きました。
(記憶だとsendMessageToGOはUaaL1に於ける「Native → Unity」間のやりとり辺りで使われていた覚え)

従来のUnityInterface.hはそもそもターゲットに含まれておらず、参照すら出来ない模様...?

Exsample.swift
import Foundation

// NOTE: ObjCに公開する物はアクセスレベルを`public` or `open`に設定する必要あり

public class Example : NSObject {

    // ObjC++からSwiftのメソッドを呼び出す
    @objc public static func callSwiftMethod(_ message: String) {
        print("\(#function) is called with message: \(message)")
    }

    // SwiftのメソッドからSendMessageを呼び出す
    @objc public static func callUnityMethod() {

        // NOTE: 従来の`UnityInterface.h`にある`SendMessage`は参照でき無さそう?なので、以下のsendMessageToGOを利用する。

        // Call a method on a specified GameObject.
        if let uf = UnityFramework.getInstance() {
            uf.sendMessageToGO(
                withName: "CallbackTarget",
                functionName: "OnCallFromSwift",
                message: "Hello, Unity!")
        }
    }
}

Objective-C++のコード

こちらの主な変更点としては、importするヘッダーであり、対象をunityswift-Swift.hからUnityFramework/UnityFramework-Swift.hに変更してます。

ExsampleBridge.mm
#import <Foundation/Foundation.h>

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

// P/Invoke
extern "C" {

    void callSwiftMethod(const char *message) {
        [Example callSwiftMethod:[NSString stringWithUTF8String:message]];
    }

    void callUnityMethod() {
        [Example callUnityMethod];
    }
}

C#のコード

C#側の呼び出しコードに変更は無いので割愛。

参考リンク


  1. UaaL・・・Unity as a Libraryの略称 

31
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
27