通过 `id` 在 Objective-C 中调用 Swift 闭包

Invoke Swift closure in Objective-C via `id`

这个问题的灵感来自于这个问题:Swift closure in array becomes nil in Objective-c,我用一个包装器 class 的变通方法回答了这个问题,但我仍然很好奇为什么不能调用,在 Objective-C 中,一个 Swift 闭包通过 id 传递。下面是一个简单的例子。

Objective-C代码:

// 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

提供 Objective-C 块没有问题:

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

但是,invoke... 中的 Objective-C 代码无法调用通过 id 传入的 Swift 闭包:

let myClassOC = MyClassOC()

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

myClassOC.invoke(myBlock)

我在这一行得到 EXC_BAD_ACCESS

   EmptyBlock emptyBlock = blk;

知道这里发生了什么吗?

原因可能是 Swift 3 中引入的对任何 Swift 类型在 Objective-C 中被表示为 id 类型的不透明对象的支持。阅读 Swift Objective-C 中的 this Apple blog post 中的值类型以获取详细信息。在那里你会看到当参数类型为 id 时 Swift 类型作为 _SwiftValue * 传递,这是一个不透明的类型。

这种方法的好处是 Swift 值类型可以存储在 Objective-C 集合中;但是缺点是您无法在 Objective-C 端转换为 Objective-C 兼容值。调试您的代码,您会看到该块作为 _SwiftValue * 而不是 Objective-C 块类型传递。

bak 参数声明为 EmptyBlock 类型,这样您的代码就可以工作了。

HTH

因为 Swift 闭包和 Objective-C 块不是一回事。如果 Swift 发现您正在调用具有 Objective-C 块类型的方法,则 Swift 会自动将闭包转换为 Objective-C 块,否则它不会这样做。正如 CRD 的回答中提到的,在 Swift 3 中,任何 Swift 值都可以表示为 Objective-C 对象,因此即使它看到它需要一个 Objective-C 对象,它仍然不知道你想要一个 Objective-C 块,因为 Swift 闭包仍然可以桥接到一个不透明的对象。

我想出从 Swift 传递它并工作的唯一方法是:

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