肇鑫的技术博客

业精于勤,荒于嬉

将协议默认实现作为`selector`进行调用

这个是在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(_:)))
    }
}