最近在使用開發(fā)過程中碰到這樣一個問題:向一個修飾為 @objc
的方法中傳入一個閉包參數(shù),示例代碼如下:
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)
方法動態(tài)調(diào)用 bar
方法并傳遞closure
的話盛龄,會報錯饰迹。
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x8).
但是如果直接通過 self.bar(block)
進行調(diào)用則不會有問題。
最終在
# How to pass closure as a parameter in perform(selector, withObject)
# How to pass block arguments through performSelector?
找到了原因:
Swift 中的 closure
閉包與 Objective-C 中的 block
并不相同余舶,因此不能在運行時階段作為id
類型的參數(shù)傳遞給一個接收id
類型參數(shù)的方法 啊鸭。
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)
關(guān)于 @convention
說明可以參考 # 每周 Swift 社區(qū)問答:@convention
@convention特性是在 Swift 2.0 中引入的,用于修飾函數(shù)類型匿值,它指出了函數(shù)調(diào)用的約定莉掂。用在以下幾個地方:
修飾 Swift 中的函數(shù)類型,調(diào)用 C 的函數(shù)時候千扔,可以傳入修飾過@convention(c)的函數(shù)類型,匹配 C 函數(shù)參數(shù)中的函數(shù)指針库正。
修飾 Swift 中的函數(shù)類型曲楚,調(diào)用 Objective-C 的方法時候,可以傳入修飾過@convention(block)的函數(shù)類型褥符,匹配 Objective-C 方法參數(shù)中的 block 參數(shù)
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()