Swift中的Selector

前言

Selector作为一个在很多Objective-C设计模式中的重要组成部分,Swift为了保证部分接口的一致性依然保留了这一概念。这篇文章时我在学习这部分内容时的遇到问题的一些总结。

虽然Swift中依然保留了对Selector的支持。但是在某些地方我们可以采用更为安全的方式来实现Objective-C中对应的部分。例如:respondsToSelector:performSelector:可以分别使用协议类型的可选链和闭包进行替换。

//某个协议 *respondsToSelector:*

//Objcctive-C版本 
if(self.delegate.respondsToSelector(Selector("HanggeSwiftMenuWillAnimateClose:"))){
    self.delegate.HanggeSwiftMenuWillAnimateClose(self)
}

//Swift版本
self.delegate?.HanggeSwiftMenuWillAnimateClose(self)

但是还是很多像timer和target/action设计模式一样的地方大量的使用了Selector。下面我们看下这部分的处理。

Swift2.2之前的情况

在Swift2.2版本之前,selector通过一个简单的字符串常量来传递。因为字符串是我们开发人员手写而且还没有自动补全这增加的程序出差的可能性。

    let button = UIButton(type: .System)
    button.addTarget(self, action: Selector(“buttonTapped:”), forControlEvents: .TouchUpInside)
    ...
    func buttonTapped(sender: UIButton){ }
    
一个很好的函数命名习惯和风格就是使用控件对象的名称作为函数的前缀。在上面的示例中,函数名称buttonTapped:对应的就是按键buttontapped事件。并且记住要传递唯一一个类型正确的sender参数,因为你有这个对象但是不用,总是好过你需要这个对象但是没有要好。

总体上来说并不是很难,但是这里又一些问题需要我们注意:

Selector的可用性:通过selector引出的方法需要暴露给ObjC运行时。如果是继承自NSObject的类那么就已经实现了这个特性,但是如果是纯Swift的类你需要在函数的声明前面加上@objc来进行实现。并且该函数访问级别最起码应该是internal,因为private是无法暴露给运行时的。

Selector的名字:因为selectors是一个ObjC中的东西遵循ObjC的方法命名规则,每一个参数都有一个冒号(:)。例如,名为test()的selector就是"test",test(this: String)的selector就是"test:",test(this: String, andThat: Int)就是"test:andThat:"。

Swift2.2中的改进

在Swift2.2中selector已经变得更加安全了。它使用#selector这种语法来实现Selector,这避免了之前使用字符串可能带来的手动输入错误。因为该语法会让编译器会对方法进行检查,不存在的方法是无法通过编译的,这个在之前做法中是做不到的。

button.addTarget(self, action: #selector(ViewController.buttonTapped(_:)), forControlEvents: .TouchUpInside)

当你一眼扫过去会发现代码比较长也不是很容易阅读。尤其是如果你的代码中有很多的ViewController类型的类并且在代码中多次使用同一个Selector,多次的拷贝/复制这么长的代码或者是修改是不是想想就很麻烦啊?一种解决方法是:将所有Selector整理放在同一个地方,进行统一的编辑和引用。

private struct Action {
    static let buttonTapped = 
        #selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: Action.buttonTapped,       
    forControlEvents: .TouchUpInside)
    

我们将所有的Selector作为静态常量放在Action结构里面。这里之所以将Action声明为private是为了防止其它文件里面也定义了一个这样的结构导致编译时候的重定义。

另一种更为语法糖一些的解决方法就是对Selector进行extension

private extension Selector {
    static let buttonTapped = 
        #selector(ViewController.buttonTapped(_:))
}
...
button.addTarget(self, action: .buttonTapped, 
    forControlEvents: .TouchUpInside)
    

利用语法特性,我们不需要使用Selector.buttonTapped,从而使代码更简洁一些,也更装X一些。


BigNerdCoding
1.2k 声望125 粉丝

个人寄语: