Передаем делегат из Unity3D в ObjC

Привет! Давно не писал статей. Вот решил поделиться недавним хаком, который пришлось использовать для написания плагина. Будет разбирать на примере как передать из C# кода ссылку на метод(делегат) в ObjC код. Исходники я приведу, так как плагинчик опенсорсный (с открытым исходным кодом).

Что делает плагин? Он перехватывает событие openUrl и возвращает результат в приложение. Это полезно, когда нужно реализовать, например, oauth регистрацию и нужно получить токен в ответ через схему приложения.

Вы наверное знаете, что есть метод UnitySendMessage. Для его работы нужно создавать GameObject, вешать на него скрипт. К тому же, вам придется писать строки с именами объекта и методов, что не очень красиво и удобно. С делегатами круче — сам по себе он несет интерфейс, вам остается лишь вызвать его когда нужно и передать необходимые параметры.

Для начала, давайте вставим готовый ObjC код в папку Assets/Plugins/iOS/

Файл UrlIntercepter.h:
//
//  UrlIntercepter.h
//  Unity-iPhone
//
//  Created by afrokick on 10.05.16.
//

#import <Foundation/Foundation.h>

typedef void (*UrlIntercepterUrlOpenedDelegate) (const char *);

@interface UrlIntercepter : NSObject

@property (assign, nonatomic) UrlIntercepterUrlOpenedDelegate urlOpenedDelegate;

@end


Обратите внимание на следующее
  1. typedef void (*UrlIntercepterUrlOpenedDelegate) (const char *); — это и есть обявление сигнатуры делегата. Его C# эквивалент выглядит delegate void UrlIntercepterUrlOpenedDelegate (String name) Похоже, правда?
  2. @property (assign, nonatomic) UrlIntercepterUrlOpenedDelegate urlOpenedDelegate; — объявляем свойство, в которое поместим слушателя. Он будет следить за сообщениями и как только придет нужно, сделает вызов делегата.

Далее тело файла UrlIntercepter.m
//
//  UrlIntercepter.m
//  Unity-iPhone
//
//  Created by afrokick on 10.05.16.
//

#import "UrlIntercepter.h"

static UrlIntercepter * _urlIntercepter;

void UrlIntercepterInitialize(UrlIntercepterUrlOpenedDelegate urlOpenedDelegate){
    _urlIntercepter = [[UrlIntercepter alloc] init];
    
    _urlIntercepter.urlOpenedDelegate = urlOpenedDelegate;
}

@interface UrlIntercepter ()<UIAlertViewDelegate>

@end

@implementation UrlIntercepter

- (id)init {
    self=[super init];
    [self registerNotificationReciever];
    return self;
}

-(void) registerNotificationReciever{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(receiveOpenUrl:)
                                                 name:@"kUnityOnOpenURL"
                                               object:nil];
}

- (void) receiveOpenUrl:(NSNotification *) notification
{
    NSString * notificationName=[notification name];
    
    if(![notificationName containsString: @"kUnityOnOpenURL"]) return;
    
    NSDictionary *userInfo = notification.userInfo;
    
    NSURL * url = [userInfo objectForKey:@"url"];
    
    if(self.urlOpenedDelegate){
        self.urlOpenedDelegate([url.absoluteString cStringUsingEncoding:NSUTF8StringEncoding]);
    }
}

@end


Первым делом объявим СТАТИЧЕСКУЮ переменную static UrlIntercepter * _urlIntercepter; для того, чтобы сборщик мусора не уничтожил наш объект.

Далее объявим метод void UrlIntercepterInitialize(UrlIntercepterUrlOpenedDelegate urlOpenedDelegate){. Имя данного метода должно совпадать с именем в C# коде. Также видно, что внутрь метода мы передаем ссылку на метод(делегат). После этого мы создаем слушателя и назначаем делегат.

Метод -(void) registerNotificationReciever{ позволяет нам подписаться на событие, которое нас интересует.

Когда мы получим событие, то попадет внутрь receiveOpenUrl. Нас интересует последние три строчки метода — это вызов делегата и передача в него строки. Обратите внимание на тип параметра. Чтобы обратно выслать строку, нам нужно преобразовать ее к массиву символов(c-string).

С ObjC частью мы разобрались. Теперь переходим к C#. Кладем файл UrlIntercepter.cs в любое место:
using System;
#if UNITY_IPHONE && !UNITY_EDITOR
using System.Runtime.InteropServices;
using AOT;
#endif

public class UrlIntercepter
{
	public delegate void UrlIntercepterUrlOpenedDelegate (String name);

	#if UNITY_IPHONE && !UNITY_EDITOR
	[DllImport("__Internal")]
	internal static extern void UrlIntercepterInitialize(UrlIntercepterUrlOpenedDelegate urlOpened);

	[MonoPInvokeCallback (typeof (UrlIntercepterUrlOpenedDelegate))]
	private static void UrlWasOpened(String name) {
		if(_listener != null){
			_listener(name);
		}
	}
	#endif

	private static UrlIntercepterUrlOpenedDelegate _listener;

	public static void Initialize(UrlIntercepterUrlOpenedDelegate listener) {
		_listener = listener;

		#if UNITY_IPHONE && !UNITY_EDITOR
		UrlIntercepter.UrlIntercepterInitialize(UrlIntercepter.UrlWasOpened);
		#endif
	}
}


Вначале объявим делегат public delegate void UrlIntercepterUrlOpenedDelegate (String name);
Далее нам нужно связать ObjC метод и C# метод UrlIntercepterInitialize.
Далее объявим метод, которым мы передадим в ObjC, и который вызовется из него. Обратите внимание, что он помечен атрибутом MonoPInvokeCallback.

Теперь где-нибудь в коде можно подписаться на событие:
UrlIntercepter.Initialize((url)=>{
			Debug.Log("UrlIntercepter:" + url);	
		});


Если у вас есть схема и вы откроете приложение через нее, например, myapp://helloFromAfro, то в консоль выведется UrlIntercepter:myapp://helloFromAfro

Все! Удачи в разработке!

PS Я не очень хорошо шарю в ARC и в челом в ObjC. Если кто-то знает как можно заставить объект не удаляться и убрать статическую переменную, поделитесь плз! Ну и в челом, если есть замечания, буду рад выслушать в комментах! Спасибо!

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.