这个是在swift-users
邮件列表遇到的。原标题是Calling default implementation of protocol methods as selectors
。原本是个很简单的问题,但是经过几轮讨论,得到的收获还是蛮大的。
先来看问题
有如下代码
protocol Foo: class {
func bar()
}
extension Foo {
func bar() {
print("bar")
}
}
class Baz: Foo {
init() {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bar)) // error: argument of '#selector' refers to instance method 'bar()' that is not exposed to Objective-C
}
}
编辑器提示无法调用这个#selector
,因为它bar()
并没有暴露给Objctive-C。而protocol
不能插入@objc
。
如何解决
方法1
不使用协议扩展,而使用基类。
protocol Foo: class {
func bar()
}
class Base:Foo {
@objc func bar() {
print("bar")
}
}
class Baz: Base {
override init() {
super.init()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bar))
}
}
方法2
使用一个代理函数。
protocol Foo: class {
func bar()
}
extension Foo {
func bar() {
print("bar")
}
}
class Baz: Foo {
init() {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(delegate))
}
@objc func delegate() {
bar()
}
}
这两个办法中,1比2好。因为1方便修改。而如果2需要扩展的话,就需要每个都写一遍。太麻烦。
方法3
我也尝试了convenience init(target: Any, closure: @escaping () -> ())
的方法,但是我放弃了。因为我发现,我没法调用closure
。
后续的邮件中,有人提出了可以内嵌一个类,来实现调用closure
。这个是他的实现。
public class BlockTapGestureRecognizer: UITapGestureRecognizer {
private class Target: NSObject {
private let closure: (UITapGestureRecognizer) -> ()
init(closure: @escaping (UITapGestureRecognizer) -> ()) {
self.closure = closure
super.init()
}
@objc func performClosure(_ sender: Any?) {
guard let recognizer = sender as? UITapGestureRecognizer else {
print("Unexpected sender (expected UITapGestureRecognizer)")
return
}
self.closure(recognizer)
}
}
private let target: Target
public init(closure: @escaping (UITapGestureRecognizer) -> ()) {
self.target = Target(closure: closure)
super.init(target: self.target, action: #selector(Target.performClosure(_:)))
}
}