@convention 使用-在 Swift 中通过 perform(selector, withObject)传递闭包参数

最近在使用开发过程中碰到这样一个问题:向一个修饰为 @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.

解决办法两种:

  1. 使用 @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()