最近在使用开发过程中碰到这样一个问题:向一个修饰为 @objc
的方法中传入一个闭包参数,示例代码如下:
class SomeClass: NSObject {
@objc func foo() {
print("foo...")
let selectorName = "bar:"
let selector = Selector(selectorName)
if self.responds(to: selector) {
let closure = { () in
print("block")
}
self.perform(selector, with: closure) //this will take error
//self.bar(block)
}
}
@objc func bar(_ arg1:@escaping ()->()) {
print("bar...")
arg1()
}
}
let someClass = SomeClass()
someClass.foo()
如果通过 self.perform(selector, with:block)
方法动态调用 bar
方法并传递closure
的话,会报错。
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x8).
但是如果直接通过 self.bar(block)
进行调用则不会有问题。
最终在
# How to pass closure as a parameter in perform(selector, withObject)
# How to pass block arguments through performSelector?
找到了原因:
Swift 中的 closure
闭包与 Objective-C 中的 block
并不相同,因此不能在运行时阶段作为id
类型的参数传递给一个接收id
类型参数的方法 。
This method is the same as perform(_:) except that you can supply an argument for aSelector. aSelector should identify a method that takes a single argument of type id.id is a pointer to an objective-c object. From Swift, you are going to be limited to passing objects that inherit from NSObject. A closure does not meet those requirements.
解决办法两种:
- 使用
@convention
对 Swift 中的闭包进行修饰
let closure = {
print("block")
}
let block: @convention(block) () -> () = closure
self.perform(selector, with: block)
关于 @convention
说明可以参考 # 每周 Swift 社区问答:@convention
@convention特性是在 Swift 2.0 中引入的,用于修饰函数类型,它指出了函数调用的约定。用在以下几个地方:
修饰 Swift 中的函数类型,调用 C 的函数时候,可以传入修饰过@convention(c)的函数类型,匹配 C 函数参数中的函数指针。
修饰 Swift 中的函数类型,调用 Objective-C 的方法时候,可以传入修饰过@convention(block)的函数类型,匹配 Objective-C 方法参数中的 block 参数
2.自定义一个Block
类对 Swift 中的闭包进行包装
class Block: NSObject {
let block: () -> ()
init(block: @escaping () -> ()) {
self.block = block
super.init()
}
}
class SomeClass: NSObject {
@objc func foo() {
print("foo...")
let selectorName = "bar:"
let selector = Selector(selectorName)
if self.responds(to: selector) {
let block = Block {
print("block")
}
self.perform(selector, with: block)
}
}
@objc func bar(_ arg1: Block) {
print("bar...")
arg1.block()
}
}
let someClass = SomeClass()
someClass.foo()