Home > Mobile >  How to pass a "completion handler" from Unity to Swift using Objective-C bridge
How to pass a "completion handler" from Unity to Swift using Objective-C bridge

Time:12-07

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 result: 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.

CodePudding user response:

The problem is Swift closures aren't exactly Objective-C blocks even though there is a huge degree of similiarity.

Keep your

void _addTwoNumberInIOS(int a , int b, void (^_Nonnull)onComplete(NSString*))

But add @convention(block) to the Swift signature like this:

@objc public func AddTwoNumber(a: Int, b: Int, onComplete: @escaping @convention(block)(String) -> Void) -> Void {

CodePudding user response:

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.

  • Related