How to pass a "completion handler" from Unity to Swift using Objective-C bridge
I am trying to create an IOS plugin to do some API calls for my Unity game.
I started with this article and got it running so that basic structuring is done.
Now to call any API and get its response in Unity, I looked up this question but I am unable to figure out how to declare my _addTwoNumberInIOS
function in Objective-C so that it can take a completionHandler
as a parameter (basically a function taking string
as argument and returning void
) and pass it to Swift code which will later call it when it has something to return.
My Unity code is:
using System.Runtime.InteropServices;
using UnityEngine;
using TMPro;
using AOT;
using System;
public class PluginHelper : MonoBehaviour
{
public static TextMeshProUGUI myText;
[DllImport("__Internal")]
private static extern void _addTwoNumberInIOS(int a, int b, Action<string> completionHandler);
[MonoPInvokeCallback ( typeof ( Action<string> ) )]
private static void completionHandler ( string s )
{
myText.text = "25 + 5 is : " + s;
}
void Start()
{
AddTwoNumber();
}
public void AddTwoNumber()
{
_addTwoNumberInIOS(25, 5, completionHandler);
}
}
My swift code is:
import Foundation
@objc public class UnityPlugin : NSObject {
@objc public static let shared = UnityPlugin()
@objc public func AddTwoNumber(a:Int,b:Int,onComplete:@escaping(String)->Void) -> Void {
let url : String = "https://restcountries.eu/rest/v2/all"
var res开发者_运维技巧ult: String = "1111"
result = "2222"
URLSession.shared.dataTask(with: NSURL(string: url) as! URL, completionHandler: { (data, response, error) in
// Handle result
result = "data "
// do api call
onComplete(result)
}).resume();
}
}
Now my Objective-C code (where I am having trouble):
#import <Foundation/Foundation.h>
#include "UnityFramework/UnityFramework-Swift.h"
extern "C" {
#pragma mark - Functions
void _addTwoNumberInIOS(int a , int b, void onComplete(NSString*)) {
[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete]; // problem here
}
}
I am getting
Cannot initialize a parameter of type 'void (^ _Nonnull)(NSString * _Nonnull __strong)' with an lvalue of type 'void (*)(NSString *__strong)'
on line[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete];
If I change my function to:
void _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*)) {
[[UnityPlugin shared] AddTwoNumberWithA:a b:b onComplete:onComplete];
}
Then I get
Block pointer to non-function type is invalid
on linevoid _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*))
Question: How do I declare and define _addTwoNumberInIOS
in Objective-C to use shared UnityPlugin Swift code so that I can return and use API response in Unity?
Thanks.
EDIT
After suggestion from here I modified my code like -
swift code:
import Foundation
@objc public class UnityPlugin : NSObject {
public typealias CCallbackFunctionWithString = @convention(c) (UnsafePointer<CChar>) -> Void;
@objc public static let shared = UnityPlugin()
@objc public func AddTwoNumber(_ a:Int, _ b:Int, _ onComplete:@escaping CCallbackFunctionWithString) -> Void {
let url : String = "http://192.168.167.158:9000"
var result: String = "1111"
result = "2222"
print("adding numbers");
URLSession.shared.dataTask(with: NSURL(string: url) as! URL, completionHandler: { (data, response, error) in
// Handle result
result = "data "
print("completing \(result)");
onComplete(result)
}).resume();
}
}
objective-c code:
#import <Foundation/Foundation.h>
#include "UnityFramework/UnityFramework-Swift.h"
extern "C"
{
typedef void (*CallbackFunctionWithString)( const char * );
// etc ...
#pragma mark - Functions
void _addTwoNumberInIOS( int a , int b, CallbackFunctionWithString onComplete )
{
[[UnityPlugin shared] AddTwoNumber:a:b:onComplete];
}
}
I now get the log completing data
but my code is crashing with exception -
libc++abi: terminating with uncaught exception of type Il2CppExceptionWrapper
terminating with uncaught exception of type Il2CppExceptionWrapper
Anyone has any suggestions why it is crashing?
When I wrote the native communications for an app of mine, I was using callbacks extensively, so a shortcut I used was to do this:
extern "C"
{
typedef void (*CallbackFunction)();
typedef void (*CallbackFunctionWithString)( const char * );
typedef void (*CallbackFunctionWithInt)( NSInteger );
typedef void (*CallbackFunctionWithFloat)( float );
// Sometimes I also wanted to send back a 'success' bool, with the string data, so I did that like this:
typedef void (*CallbackFunctionWithBoolAndString)( bool, const char * );
// etc ...
#pragma mark - Functions
void _addTwoNumberInIOS( int a , int b, CallbackFunctionWithBoolAndString onComplete )
{
[UnityPlugin shared] AddTwoNumberWithA:a:b:onComplete;
}
}
The Swift file would then look like this:
import Foundation
typealias CCallbackFunction = @convention(c) () -> Void;
typealias CCallbackFunctionWithString = @convention(c) (UnsafePointer<CChar>) -> Void;
typealias CCallbackFunctionWithInt = @convention(c) (Int) -> Void;
typealias CCallbackFunctionWithFloat = @convention(c) (Float) -> Void;
typealias CCallbackFunctionWithBool = @convention(c) (Bool) -> Void;
typealias CCallbackFunctionWithBoolAndString = @convention(c) (Bool, UnsafePointer<CChar>) -> Void;
// etc ..
@objc public class UnityPlugin : NSObject
{
@objc public static let shared = UnityPlugin()
@objc public func AddTwoNumber( _ a:Int, _ b:Int, _ onComplete:@escaping CCallbackFunctionWithString ) -> Void {
let url : String = "https://restcountries.eu/rest/v2/all"
var result: String = "1111"
result = "2222"
URLSession.shared.dataTask(with: NSURL(string: url) as! URL, completionHandler: { (data, response, error) in
// Handle result
result = "data "
onComplete(result)
}).resume();
}
}
Mind you this was back in the wild west days of Swift 4, so YMMV. But this worked a treat because, as mentioned, I relied heavily on callbacks to tell Unity when tasks in the native libraries had finished.
精彩评论