Anatoli P
Anatoli P

Reputation: 4891

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?

Upvotes: 1

Views: 919

Answers (2)

newacct
newacct

Reputation: 122518

Because Swift closures and Objective-C blocks are not the same things. Swift automatically converts a closure to an Objective-C block if they see that you are calling a method with an Objective-C block type, but it doesn't do that otherwise. And as CRD's answer mentions, in Swift 3 any Swift value can be represented as an Objective-C object, so even though it sees that it expects an Objective-C object, it still doesn't know you want an Objective-C block because a Swift closure can still be bridged to an opaque object.

The only way I figured out to pass it from Swift and work is something like:

myClassOC.invoke(myBlock as @convention(block) () -> Void)

Upvotes: 2

CRD
CRD

Reputation: 53010

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

Upvotes: 3

Related Questions