在使用Swift的过程中,可能会遇到包含 #selectore 的代码:
helpMenu.addItem(withTitle: "访问帮助页面", action: #selector(openHelp), keyEquivalent: "?")
#selector 是 Swift 中用来引用 Objective-C 方法(即“选择器”)的语法,它的作用是告诉系统:当某个事件发生时,应该调用哪个方法。
什么是 Selector?
Selector 是 Objective-C 世界中的一个概念,表示方法的名称,用于动态调用方法。
例如:
@objc func quitApp() {
NSApp.terminate(nil)
}
可以用 Selector 来引用这个方法:
let quitItem = NSMenuItem(title: "退出", action: #selector(quitApp), keyEquivalent: "q")
系统就知道,当用户点击这个菜单项时,要调用 quitApp() 方法。
语法说明
#selector(方法名)
或者如果方法带参数:
#selector(methodName(_:))
比如:
@objc func showAlert(_ sender: Any?) {
// ...
}
button.action = #selector(showAlert(_:))
为什么要加 @objc?
因为 Selector 属于 Objective-C 的运行时机制,Swift 方法默认是静态调度的(编译时确定),必须加 @objc 才能暴露给运行时,也才能被 #selector 使用。
@objc func xxx() {} // 可以 selector
func xxx() {} // 编译错误(不是 @objc)
自动传参机制
在#selector()中虽然没有显式传参,但是在调用方法的时候,会自动传入触发该实际的对象。
例如,在NSMenuItem中,action会调用openApp方法:
let openItem = NSMenuItem(title: "打开 App", action: #selector(openApp), keyEquivalent: "o")
openItem.target = self
@objc func openApp() {
print("打开 App")
NSApp.activate(ignoringOtherApps: true)
}
在openApp中设置一个open参数,类型为Any:
@objc func openApp(_ open: Any) {
print("传入的是\(type(of:open))")
print("打开 App")
NSApp.activate(ignoringOtherApps: true)
}
当调用openApp方法时,实际上输出的是:
传入的是NSMenuItem
打开 App
这里传递的open参数实际上就是NSMenuItem本身。
因此,可以通过自动传参机制修改调用的对象。
为什么可以传递参数?
因为,在触发action时,macOS会自动尝试传入一个参数(点击的NSMenuItem)。
如果方法是@obj func openApp()(不带参数),只要运行环境能匹配到不带参数的方法,就会调用该方法。
背后的机制在于,Selector是一个函数签名标识:
#selector(openApp)
它最终会被翻译为Objective-C的方法表示:
@selector(openApp)
或
@selector(openApp:)
这取决于有没有写参数。
在macOS中,当点击NSMenuItem菜单项时,系统会执行:
[target performSelector:action withObject:self]
也就是说它默认会把当前的菜单项(自己)作为参数传入,相当于 Swift 中的:
target.openApp(menuItem)
没有参数
如果写成:
@objc func openApp()
然后调用:
let item = NSMenuItem(title: "打开", action: #selector(openApp), keyEquivalent: "o")
item.target = self
只要系统找到 @objc func openApp() 这个方法,它就能调用。虽然系统本来是想传一个参数的,但这个方法只接受 0 个参数,于是系统会改为调用:
[target openApp] // 无参数版本
macOS 会尽力找最匹配的方法 —— 如果找不到,才会报错;如果能调用 0 参数版本,就默认调用它。
总结
#selector是Swift 中引用 Objective-C 方法的一种语法,常用于按钮、菜单、通知等回调触发,被引用的方法必须加 @objc 修饰。
在SwiftUI中使用闭包处理事件:
Button("退出") {
NSApp.terminate(nil)
}
而 AppKit / UIKit 中使用 Selector,更接近 Objective-C 风格。