协慌网

登录 贡献 社区

Swift 中的 @selector()?

我正在尝试在Swift NSTimer ,但遇到了一些麻烦。

NSTimer(timeInterval: 1, target: self, selector: test(), userInfo: nil, repeats: true)

test()是同一类中的函数。


我在编辑器中收到一个错误:

找不到接受提供的参数的'init' 的重载

当我将selector: test()更改为selector: nil ,错误消失了。

我试过了:

  • selector: test()
  • selector: test
  • selector: Selector(test())

但是没有任何效果,我无法在参考文献中找到解决方案。

答案

Swift本身不使用选择器 - 在 Objective-C 中,使用选择器的几种设计模式在 Swift 中的工作方式有所不同。 (例如,使用任选的链上的协议类型或is / as测试来代替respondsToSelector:和使用闭随时随地可以代替performSelector:为更好的类型 / 存储器的安全性。)

但是仍然有许多重要的基于 ObjC 的 API 使用选择器,包括计时器和目标 / 操作模式。 Swift 提供了用于处理这些内容Selector (Swift 会自动使用它代替 ObjC 的SEL类型。)

在 Swift 2.2(Xcode 7.3)和更高版本(包括 Swift 3 / Xcode 8 和 Swift 4 / Xcode 9)中:

您可以#selector表达式从 Swift 函数类型Selector

let timer = Timer(timeInterval: 1, target: object,
                  selector: #selector(MyClass.test),
                  userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
                 for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
             with: button, with: otherButton)

这种方法的好处是什么? Swift 编译器会检查函数引用,因此您只能将#selector表达式与实际存在且有资格用作选择器的类 / 方法对一起使用(请参见下面的 “选择器可用性”)。 根据 Swift 2.2 + 函数类型命名规则,您还可以根据需要自由地仅对函数进行引用。

(这实际上是对 ObjC 的@selector()指令的改进,因为编译器的-Wundeclared-selector检查仅验证命名选择器的存在。传递给#selector的 Swift 函数引用将检查存在性,类的成员身份和类型签名。 )

#selector表达式的函数引用,还有一些其他警告:

  • 使用上述用于函数引用的语法(例如, insertSubview(_:at:)insertSubview(_:aboveSubview:) ),可以通过参数标签来区分具有相同基本名称的多个函数。但是,如果一个函数没有参数,消除歧义的唯一方式是使用as投用函数的类型签名(例如foo as () -> () VS foo(_:) )。
  • Swift 3.0 + 中的属性 getter / setter 对有一种特殊的语法。例如,给定var foo: Int ,可以使用#selector(getter: MyClass.foo)#selector(setter: MyClass.foo)

一般注意事项:

#selector无法使用和命名的情况:有时您没有函数引用来创建选择器(例如,使用在 ObjC 运行时中动态注册的方法)。在这种情况下,您可以从字符串Selector Selector("dynamicMethod:") —尽管您丢失了编译器的有效性检查。当你这样做,你需要遵循 ObjC 的命名规则,包括冒号( : )每个参数。

选择器可用性:选择器引用的方法必须公开给 ObjC 运行时。在 Swift 4 中,每个公开给 ObjC 的方法都必须在其声明的前面加上@objc属性。 (在以前的版本中,某些情况下您可以免费获得该属性,但是现在您必须显式声明它。)

请记住, private符号也不会暴露给运行时 - 您的方法至少需要具有internal可见性。

关键路径:这些路径与选择器相关,但并不完全相同。 Swift 3 中也有特殊的语法:例如chris.valueForKeyPath(#keyPath(Person.friends.firstName)) 。有关详细信息,请参见SE-0062。还有Swift 4 中的KeyPath内容,因此请确保您使用的是正确的基于 KeyPath 的 API,而不是选择器(如果适用)。

您可以在 “将 Swift 与 Cocoa 和 Objective-C 结合使用” 中的 “ 与 Objective-C API 交互”下阅读有关选择器的更多信息。

注意:在 Swift 2.2 之前, Selector符合StringLiteralConvertible ,因此您可能会发现旧代码,其中裸露的字符串被传递到采用选择器的 API。您将需要在 Xcode 中运行 “转换为当前的 Swift 语法”,以使用#selector获得这些内容。

这是一个有关如何在 Swift 上Selector

override func viewDidLoad() {
    super.viewDidLoad()

    var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))
    self.navigationItem.rightBarButtonItem = rightButton
}

func method() {
    // Something cool here   
}

请注意,如果作为字符串传递的方法不起作用,它将在运行时失败,而不是在编译时失败,并导致应用崩溃。小心

另外,如果您的(Swift)类不是 Objective-C 类的子类,那么您必须在目标方法名称字符串的末尾加一个冒号,并且必须将 @objc 属性与目标方法一起使用,例如

var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))

@objc func method() {
    // Something cool here   
}

否则,您将在运行时收到 “无法识别的选择器” 错误。