通过 `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)
这个问题的灵感来自于这个问题: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)