Unity Native Pluginの作り方（iOS編）

#概要
iOS向けに開発したSDKをUnityアプリに対応させる場合、プラグインを作ってSDKとUnityアプリの橋渡しをしてやる必要があります。本記事ではUnity側のコードをC#、プラグイン側のコードをObjective-C++ (.mm)で記載するケースについて紹介します。 

#補足
iOS, Android共通で使用するコードについては[共通編](https://qiita.com/maebaru/items/ff6c414427b4512a10c5)を参考にして下さい。

#プラグイン側
まずはプラグイン側のコードの一例です。Sample, SampleDelegateといったクラスはSDK側で定義されており、それらのメソッドにUnity側からアクセスするために各Wrapperメソッドを実装しています。このコードはUnityプロジェクトのAssets/Plugins/iOSディレクトリ配下に入れておくと良いでしょう。

```objective_c:SamplePlugin.mm
#include <stdio.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <SampleSDK/SampleSDK.h>
#import <SapleSDK/SampleSDK-Swift.h>

#define kSample_DefaultCallbackGameObjectName @"Main Camera"

@interface SamplePlugin: NSObject
@end

SamplePlugin *__unityClientSharedInstance;

@interface SamplePlugin() <SampleDelegate>
@property(nonatomic, strong) NSString *callbackGameObjectName;

- (void)invokeUnityGameObjectMethod:(NSString *)methodName message:(NSString *)message;

@end

@implementation SamplePlugin

+ (SamplePlugin *)sharedInstance {
    if (!__unityClientSharedInstance) {
        __unityClientSharedInstance = [[SamplePlugin alloc] init];
    }
    
    return __unityClientSharedInstance;
}

# pragma mark - SampleDelegate

- (void)didStatusChange:(enum SampleStatus)status {
    NSString *statusStr = [NSString stringWithFormat:@"%ld", status];
    [self invokeUnityGameObjectMethod:@"_Sample_didStatusChange" message:statusStr];
}

# pragma mark - Private

- (void)invokeUnityGameObjectMethod:(NSString *)methodName message:(NSString *)message {
    NSString *gameObject = self.callbackGameObjectName == nil ?
    kSample_DefaultCallbackGameObjectName : self.callbackGameObjectName;
    
    const char *gameObjectStr = [gameObject cStringUsingEncoding:NSUTF8StringEncoding];
    const char *methodNameStr = [methodName cStringUsingEncoding:NSUTF8StringEncoding];
    const char *messageStr = [message cStringUsingEncoding:NSUTF8StringEncoding];
    
    UnitySendMessage(gameObjectStr, methodNameStr, messageStr);
}

@end

// Declare each wrapper methods
extern "C" {
    void SPStart(const char *sampleCode);
    void SPDoSomething();
    int SPGetStatus();
    int SPGetScore();
    bool SPIsAvailable();
    const char* SPGetSomeText();
}

extern UIViewController* UnityGetGLViewController();
extern void UnitySendMessage(const char* obje, const char* method, const char* msg);

void SPSetCallbackGameObjectName(char *gameObjectName) {
    NSString *name = [NSString stringWithCString:gameObjectName encoding:NSUTF8StringEncoding];
    SamplePlugin *client = [SamplePlugin sharedInstance];
    client.callbackGameObjectName = name;
}

void SPStart(const char *sampleCode) {
    NSString *sampleCodeStr = [NSString stringWithCString:sampleCode encoding:NSUTF8StringEncoding];
    
    [[Sample sharedInstance] startWithSampleCode: sampleCodeStr];
    [Sample sharedInstance].delegate = [SamplePlugin sharedInstance];
}

void SPDoSomething() {
    [[Sample sharedInstance] doSomething];
}

int SPGetStatus() {
    return [[Sample sharedInstance] getStatus];
}

int SPGetScore() {
    return [[Sample sharedInstance] getScore];
}

bool SPIsAvailable() {
    return [[Sample sharedInstance] isAvailable];
}

const char* SPGetSomeText() {
    NSString *textStr = [[Sample sharedInstance] getSomeText];
    const char *textChar = [textStr cStringUsingEncoding:NSUTF8StringEncoding];
    
    return textChar ? strdup(textChar) : NULL;
}
```

#Unity側

```c#:Sample_iOS.cs
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

#if UNITY_IOS

public class Sample_iOS : ISample {
    private Sample sampleGameObject;
    private SampleEventListener listener;

    [DllImport ("__Internal")]
    protected static extern void SPSetCallbackGameObjectName(string gameObjectName);

    public Sample_iOS(Sample sampleParent) {
        sampleGameObject = sampleParent;

        if (sampleParent.iosAppCode != null) {
            Start(null);
        }

        CreateListenerObject();
    }

    private void CreateListenerObject()	{
        listener = sampleGameObject.gameObject.AddComponent<SampleEventListener>();
        listener.SetNativeParent(this);

        SPSetCallbackGameObjectName(sampleGameObject.gameObject.name);
    }

    [DllImport("__Internal")]
    private static extern void SPStart(string sampleCode);
    public void Start(string sampleCode) {
        if (sampleGameObject.iosSampleCode != null) {
            SPStart(sampleGameObject.iosSampleCode);
        } else if (sampleCode != null) {
            SPStart(sampleCode);
        }
    }

    [DllImport("__Internal")]
    private static extern void SPDoSomething();
    public void DoSomething() {
        SPDoSomething();
    }

    [DllImport("__Internal")]
    private static extern int SPGetStatus();
    public SampleStatus GetStatus() {
        return (SampleStatus)SPGetStatus();
    }

    [DllImport("__Internal")]
    private static extern int SPGetScore();
    public int GetScore() {
        return SPGetScore();
    }

    [DllImport("__Internal")]
    private static extern bool SPIsAvailable();
    public bool IsAvailable() {
        return SPIsAvailable();
    }

    [DllImport("__Internal")]
    private static extern string SPGetSomeText();
    public string GetSomeText() {
        return SPGetSomeText();
    }
}

#endif
```

#参考
https://docs.unity3d.com/ja/540/Manual/PluginsForIOS.html