开发者

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 line void _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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜