Swift closure in array becomes nil in Objective-c

前端 未结 2 479
死守一世寂寞
死守一世寂寞 2020-12-21 10:14

I created an objective-c method which will invoke a method via NSInvocation:

typedef void (^ScriptingEmptyBlock)();
typedef void (^ScriptingErrorBlock)(NSErr         


        
相关标签:
2条回答
  • 2020-12-21 10:38

    success is not nil (in fact, NSArray cannot contain nils). If you print it like NSLog(@"%@", success);, it will say (Function), not (null). And if you print its class like NSLog(@"%@", [success class]);, it will say _SwiftValue. Basically, it is a Swift value that is bridged into Objective-C.

    The problem is that the object success points to is not an Objective-C block. It is a Swift closure, and Swift closures are not the same as Objective-C blocks. Trying to use invoke it as if it were an Objective-C block causes undefined behavior. po in the debugger prints it wrong probably because it is printing it assuming it were type ScriptingEmptyBlock (a block type). If you do po (id) success, it will print (Function).

    As to how you can explicitly put an Objective-C block into the array from Swift, the only way I figured out to do it something like:

    let params:[Any] = [ "testName",
                         successClosure as (@convention(block) () -> Void)!,
                         errorClosure as (@convention(block) (NSError) -> Void)!]
    object.scripting_execute (ScriptOperation.updateProductName.rawValue,
                              withParams: params)
    

    I am not sure why it's necessary to put the function type inside a !, but it doesn't seem to work otherwise. Maybe someone else can find a better way.

    0 讨论(0)
  • 2020-12-21 10:54

    I must admit that I don't fully understand why this is happening, but as far as I can tell this has nothing to do with using NSInvocation and would happen even if we just passed a Swift closure to an Objective-C function via a parameter of type id. Passing an Objective-C block via id works just fine, not sure why: Swift closures are supposed to be compatible with Objective-C blocks. As you know, elements of NSArray are of type id, so any Objective-C object can be an array element.

    To work around this problem of accessing a Swift closure passed via id in Objective-C one can introduce a wrapper class.

    // In a header:
    @interface EmptyBlockWrapper : NSObject
    @property EmptyBlock _blk;
    @end
    
    // In an implementation file (just an empty implementation):
    @implementation EmptyBlockWrapper
    
    @end
    

    Then we can use a wrapper instance instead of a block as an array element in Swift:

    let myBlock : EmptyBlock = {
        print("In Swift EmptyBlock...")
    }
    
    let myBlockWrapper = EmptyBlockWrapper()
    myBlockWrapper._blk = myBlock
    

    In an Objective-C method we can call it as follows, assuming args is NSArray *:

    EmptyBlockWrapper * emptyBlockWrapper = args[1];
    emptyBlockWrapper._blk();
    

    Hopefully this is helpful. Of course, this is just a simplified example to give you an idea; this could be made much fancier.

    0 讨论(0)
提交回复
热议问题