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())

但是什么都不起作用,我在参考文献中找不到解决方案。

348070 次浏览

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:) vsinsertSubview(_:aboveSubview:))。但是如果函数没有参数,消除歧义的唯一方法是使用带有函数类型签名的as强制转换(例如foo as () -> () vsfoo(_:))。
  • 在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中的EYZ1内容,因此请确保您使用正确的基于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
}

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

选择器是Objective-C中方法名称的内部表示形式。在Objective-C中,“@selector(方法名)”会将源代码方法转换为SEL的数据类型。由于您不能在Swift中使用@selector语法(rickster在那里很重要),您必须手动指定方法名称为String对象直接,或者通过将String对象传递给选择器类型。这是一个例子:

var rightBarButton = UIBarButtonItem(
title: "Logout",
style: UIBarButtonItemStyle.Plain,
target: self,
action:"logout"
)

var rightBarButton = UIBarButtonItem(
title: "Logout",
style: UIBarButtonItemStyle.Plain,
target: self,
action:Selector("logout")
)

此外,如果您的(Swift)类不是Objective-C类的后代,那么您必须在目标方法名称字符串的末尾有一个冒号,并且您必须在目标方法(例如:

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


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

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

您可以像下面这样创建选择器。
1.

UIBarButtonItem(
title: "Some Title",
style: UIBarButtonItemStyle.Done,
target: self,
action: "flatButtonPressed"
)

2.

flatButton.addTarget(self, action: "flatButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)

请注意,@selector语法已经消失,取而代之的是一个简单的String,命名要调用的方法。有一个领域,我们都同意冗长的阻碍。当然,如果我们声明有一个名为的目标方法,我们最好写一个:

func flatButtonPressed(sender: AnyObject) {
NSLog("flatButtonPressed")
}

设置计时器:

    var timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("flatButtonPressed"),
userInfo: userInfo,
repeats: true)
let mainLoop = NSRunLoop.mainRunLoop()  //1
mainLoop.addTimer(timer, forMode: NSDefaultRunLoopMode) //2 this two line is optinal

为了是完整的,这里是平面按钮按

func flatButtonPressed(timer: NSTimer) {
}

注意在哪里设置触发操作的控件可能很有用。

例如,我发现在设置UIBarButtonItem时,我必须在viewDi Load中创建按钮,否则我会得到一个无法识别的选择器异常。

override func viewDidLoad() {
super.viewDidLoad()


// add button
let addButton = UIBarButtonItem(image: UIImage(named: "746-plus-circle.png"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("addAction:"))
self.navigationItem.rightBarButtonItem = addButton
}


func addAction(send: AnyObject?) {
NSLog("addAction")
}

以防其他人有同样的问题,我与NSTinger没有其他答案解决了这个问题,非常重要的是要提到,如果您使用的类不直接继承NSObject或在层次结构的深处(例如手动创建的swft文件),即使指定如下,其他答案也不会起作用:

let timer = NSTimer(timeInterval: 1, target: self, selector: "test",
userInfo: nil, repeats: false)
func test () {}

除了使类从NSObject继承之外,没有改变任何其他内容,我停止了“无法识别的选择器”错误,并使我的逻辑按预期工作。

Create Refresh control using Selector method.
var refreshCntrl : UIRefreshControl!
refreshCntrl = UIRefreshControl()
refreshCntrl.tintColor = UIColor.whiteColor()
refreshCntrl.attributedTitle = NSAttributedString(string: "Please Wait...")
refreshCntrl.addTarget(self, action:"refreshControlValueChanged", forControlEvents: UIControlEvents.ValueChanged)
atableView.addSubview(refreshCntrl)

//刷新控制方法

func refreshControlValueChanged(){
atableView.reloadData()
refreshCntrl.endRefreshing()


}

如果你想从NSTinger向函数传递参数,那么这是你的解决方案:

var somethingToPass = "It worked"


let timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "tester:", userInfo: somethingToPass, repeats: false)


func tester(timer: NSTimer)
{
let theStringToPrint = timer.userInfo as String
println(theStringToPrint)
}

在选择器文本(tester:)中包含冒号,您的参数进入userInfo。

您的函数应该将NSTinger作为参数。然后只需提取userInfo以获取通过的参数。

更改为调用选择器语法的方法中的简单字符串命名

var timer1 : NSTimer? = nil
timer1= NSTimer(timeInterval: 0.1, target: self, selector: Selector("test"), userInfo: nil, repeats: true)

之后,键入func test()。

Swift4.0

您可以像下面这样创建选择器。

1.add事件到一个按钮,如:

button.addTarget(self, action: #selector(clickedButton(sender:)), for: UIControlEvents.touchUpInside)

函数如下所示:

@objc func clickedButton(sender: AnyObject) {


}

对于未来的读者,我发现我遇到了一个问题,并且得到了一个unrecognised selector sent to instance错误,该错误是由将目标func标记为私有引起的。

func必须是公开可见的,可以由引用选择器的对象调用。

我发现其中许多答案都很有帮助,但不清楚如何使用不是按钮的东西来做到这一点。我正在快速向UILabel添加手势识别器,并且很挣扎,所以这是我在阅读了上面的所有内容后发现对我有用的东西:

let tapRecognizer = UITapGestureRecognizer(
target: self,
action: "labelTapped:")

其中“选择器”被声明为:

func labelTapped(sender: UILabel) { }

请注意,它是公共的,我没有使用Selector()语法,但也可以这样做。

let tapRecognizer = UITapGestureRecognizer(
target: self,
action: Selector("labelTapped:"))

使用performSelector()

/addtarget()/NStimer.scheduledTimerWithInterval()方法您的方法(匹配选择器)应标记为

@objc
For Swift 2.0:
{
//...
self.performSelector(“performMethod”, withObject: nil , afterDelay: 0.5)
//...




//...
btnHome.addTarget(self, action: “buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
//...


//...
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector : “timerMethod”, userInfo: nil, repeats: false)
//...


}


@objc private func performMethod() {
…
}
@objc private func buttonPressed(sender:UIButton){
….
}
@objc private func timerMethod () {
….
}

对于Swift 2.2, 您需要编写'#selector()'而不是字符串和选择器名称,因此拼写错误和崩溃的可能性将不再存在。下面是示例

self.performSelector(#selector(MyClass.performMethod), withObject: nil , afterDelay: 0.5)

Swift 2.2+和Swift 3更新

使用新的#selector表达式,它消除了使用字符串文字的需要,从而减少了使用错误的可能性。供参考:

Selector("keyboardDidHide:")

成为

#selector(keyboardDidHide(_:))

请参阅:Swift演进提案

注意(Swift 4.0):

如果使用#selector,则需要将函数标记为@objc

示例:

@objc func something(_ sender: UIButton)

使用#选择器将在编译时检查你的代码,以确保你要调用的方法确实存在。更好的是,如果该方法不存在,你会得到一个编译错误:Xcode将拒绝构建你的应用程序,从而消除另一个可能的错误来源。

override func viewDidLoad() {
super.viewDidLoad()


navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .Add, target: self,
action: #selector(addNewFireflyRefernce))
}


func addNewFireflyReference() {
gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
}
// for swift 2.2
// version 1
buttton.addTarget(self, action: #selector(ViewController.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(ViewController.tappedButton2(_:)), forControlEvents: .TouchUpInside)


// version 2
buttton.addTarget(self, action: #selector(self.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(self.tappedButton2(_:)), forControlEvents: .TouchUpInside)


// version 3
buttton.addTarget(self, action: #selector(tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(tappedButton2(_:)), forControlEvents: .TouchUpInside)


func tappedButton() {
print("tapped")
}


func tappedButton2(sender: UIButton) {
print("tapped 2")
}


// swift 3.x
button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)


func tappedButton(_ sender: UIButton) {
// tapped
}


button.addTarget(self, action: #selector(tappedButton(_:_:)), for: .touchUpInside)


func tappedButton(_ sender: UIButton, _ event: UIEvent) {
// tapped
}

自从Swift 3.0发布以来,它甚至更微妙地声明一个目标动作合适

class MyCustomView : UIView {


func addTapGestureRecognizer() {


// the "_" is important
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyCustomView.handleTapGesture(_:)))
tapGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(tapGestureRecognizer)
}


// since Swift 3.0 this "_" in the method implementation is very important to
// let the selector understand the targetAction
func handleTapGesture(_ tapGesture : UITapGestureRecognizer) {


if tapGesture.state == .ended {
print("TapGesture detected")
}
}
}

对于斯威夫特3

let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.test), userInfo: nil, repeats: true)

函数声明在同一个类中:

@objc func test()
{
// my function
}

Swift 4.1
带有点击手势的样本

let gestureRecognizer = UITapGestureRecognizer()
self.view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.addTarget(self, action: #selector(self.dismiss(completion:)))


// Use destination 'Class Name' directly, if you selector (function) is not in same class.
//gestureRecognizer.addTarget(self, action: #selector(DestinationClass.dismiss(completion:)))




@objc func dismiss(completion: (() -> Void)?) {
self.dismiss(animated: true, completion: completion)
}

有关更多详细信息,请参阅Apple的文档:选择表达式

对于Swift 3

//创建计时器的示例代码

Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)


WHERE
timeInterval:- Interval in which timer should fire like 1s, 10s, 100s etc. [Its value is in secs]
target:- function which pointed to class. So here I am pointing to current class.
selector:- function that will execute when timer fires.


func updateTimer(){
//Implemetation
}


repeats:- true/false specifies that timer should call again n again.

Swift 4中的选择器:

button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)

Objective-C选择器

选择器标识一个方法。

//Compile time
SEL selector = @selector(foo);


//Runtime
SEL selector = NSSelectorFromString(@"foo");

例如

[object sayHello:@"Hello World"];
//sayHello: is a selector

selector是来自Objective-C世界的一个单词,您可以从Swift使用它来有可能从Swift调用Objective-C它允许您在运行时执行一些代码

Swift 2.2之前,语法是:

Selector("foo:")

由于函数名称作为String参数(“foo”)传递到Selector中,因此不可能在编译时间检查名称。结果你可以得到一个运行时错误:

unrecognized selector sent to instance

Swift 2.2+之后,语法是:

#selector(foo(_:))

Xcode的自动完成功能可帮助您调用正确的方法

正如许多人所说的选择器是一种动态调用方法的客观c方式,这种方法已经被带到了Swift,在某些情况下,我们仍然坚持使用它,比如UIKit,可能是因为他们在SwiftUI上工作以取代它,但是一些api有更像Swift Timer的版本,例如您可以使用

class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: @escaping (Timer) -> Void) -> Timer

相反,你可以这样称呼它

Timer.scheduledTimer(withTimeInterval: 1,
repeats: true ) {
... your test code here
}

Timer.scheduledTimer(withTimeInterval: 1,
repeats: true,
block: test)

其中方法测试采用Timer参数,或者如果您希望测试采用命名参数

Timer.scheduledTimer(withTimeInterval: 1,
repeats: true,
block: test(timer:))

您还应该使用Timer而不是NSTimer,因为NSTimer是旧的Objective-c名称