Search code examples
objective-cswiftclosuresblock

Invoke Swift closure in Objective-C via `id`


This question has been inspired by this one: Swift closure in array becomes nil in Objective-c, which I answered with a workaround using a wrapper class, but I'm still curious as to why one can't call, in Objective-C, a Swift closure passed via id. A simple example follows.

Objective-C code:

// In a header
typedef void (^EmptyBlock)();

@interface MyClassOC : NSObject

-(void)invoke:(id)blk;

@end

// In an implementation (.m) file
@implementation MyClassOC
-(void)invoke:(id)blk {
    EmptyBlock emptyBlock = blk;
    emptyBlock();
}
@end

No problem providing an Objective-C block:

EmptyBlock block = ^{ puts("In OC empty block..."); };
MyClassOC * myClassOC = [[MyClassOC alloc] init];
[myClassOC invoke:block];

However, the Objective-C code in invoke... can't call a Swift closure passed in via id:

let myClassOC = MyClassOC()

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

myClassOC.invoke(myBlock)

I end up with EXC_BAD_ACCESS on this line:

   EmptyBlock emptyBlock = blk;

Any idea what's going on here?


Solution

  • The reason is probably the support introduced in Swift 3 for any Swift type to be represented as an opaque object of id type in Objective-C. Read Swift Value Types in Objective-C in this Apple blog post for details. In that you will see that Swift types are passed as _SwiftValue * when the parameter type is id, this is an opaque type.

    The argued benefit of this approach is that Swift value types can be stored in Objective-C collections; however the disadvantage is that you cannot convert to an Objective-C compatible value on the Objective-C side. Debug your code and you'll see the block is being passed as a _SwiftValue * and not an Objective-C block type.

    Declare the bak parameter to have EmptyBlock type and your code works.

    HTH