Swift中的@selector()?


659

我正在尝试创建一个NSTimerin,Swift但是遇到了一些麻烦。

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

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


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

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

当我更改selector: test()selector: nil错误时消失。

我试过了:

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

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


11
selector: test()会调用test并将其返回值传递给selector参数。
Michael Dorst 2014年

Answers:


930

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使用#selector表达式从Swift函数类型构造一个。

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检查仅验证命名选择器的存在。传递给您的Swift函数引用用于#selector检查是否存在,类的成员身份和类型签名。)

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

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

一般注意事项:

情况下#selector无法正常工作,并命名:有时候你没有一个函数的引用,使一个选择器(例如,用在ObjC运行时动态注册的方法)。在这种情况下,可以Selector从字符串构造a :例如Selector("dynamicMethod:")-尽管丢失了编译器的有效性检查。执行此操作时,需要遵循ObjC命名规则,包括:每个参数的冒号()。

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

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

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

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

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


8
将带有函数名称的字符串放入,NSSelectorFromString()也起作用。
Arbitur 2014年

7
我想提及的是,虽然“与Objective-C API交互”已在网站上,但它不在“ Swift编程语言”书中。

10
这可能应该提到,如果选择器接受参数,则最后需要一个“:”。(例如test()->“ test”&test(this:String)->“ test:”)
Daniel Schlaug 2014年

2
还应该指出,Cocoa框架期望使用Objective-C样式方法名称。如果你的方法需要一个参数,您将需要一个“:”如果它需要两个参数, size:andShape:如果第一个参数被命名你可能需要一个With,即initWithData:对于func init(Data data: NSData)
JMFR

6
无论如何,围绕“选择器”作为字符串传递添加验证?当我们拼错IE编译器警告我们,等等
yo.ian.g

86

这是一个有关如何Selector在Swift 上使用该类的简单示例:

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   
}

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


13
哪个太可怕了……是否有“ NSStringFromSelector”类型的东西?
莉娜·布鲁

11
不能相信他们是为未经检查的选择器而设计的,因为objc拥有此功能
malhal 2014年

4
@malcomhall:@selector很方便,但是没有像您想象的那样正式执行。“未声明的选择器”仅是编译器的警告,因为新的选择器始终可以在运行时引入。不过,在Swift中可验证/重构的选择器引用将是一个很好的功能请求
rickster 2014年

2
这个答案很有帮助,但是下面的@objc答案更合适。
cynistersix 2014年

当将选择器字符串作为变量或参数传入时,需要使用Selector()函数让编译器知道其选择器。谢谢
levous

46

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

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

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

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


3
1.带冒号的选择器必须带有参数2.根据Apple文档,计时器的操作应带有NSTimer参数3. Selector关键字不是强制性的。因此,在这种情况下,签名必须为@objc func method(timer: NSTimer) {/*code*/}
Yevhen Dubinin 2014年

@objc为我工作。我不需要timer: NSTimer在方法签名中包含要调用的签名。
Mike Taverne

32

Swift 2.2+和Swift 3更新

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

Selector("keyboardDidHide:")

变成

#selector(keyboardDidHide(_:))

另请参阅:Swift Evolution提案

注意(Swift 4.0):

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

例:

@objc func something(_ sender: UIButton)


26

迅捷4.0

您可以如下所示创建选择器。

1.将事件添加到如下按钮:

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

功能将如下所示:

@objc func clickedButton(sender: AnyObject) {

}

4
你忘了@objc之前func它在斯威夫特4.要求
瓦希德·阿米里

24

对于将来的读者,我发现我遇到了一个问题,并遇到了unrecognised selector sent to instance由于将目标标记func为私有而导致的错误。

func 必须公开显示通过与到选择的参考对象被调用。


13
它不具备成为公众仍可以保留该方法的私人但增加objc它的声明之前。例如:@objc private func foo() { ...那么您可以随意使用它"foo"作为选择器
apouche 2015年

也可以是internal,因此不指定任何访问修饰符。我经常使用这种模式://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
Sajjon

19

万一其他人遇到与NSTimer相同的问题,而其他答案都没有解决这个问题,那么非常重要的是要提到的是,如果您使用的类既不直接继承自NSObject,也不继承于层次结构的深层(例如,手动创建的swift文件),即使按以下方式指定,其他答案也不会起作用:

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

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


这种替代方法的问题是,鉴于需要ViewController类实现的东西(例如viewDidLoad()),因此无法更改从NSObject继承的类(例如ViewController)。任何想法如何使用NSTimer在ViewController中调用Swift函数?... e
eharo2 2014年

1
UIViewController已经从NSObject继承了,大多数SDK公开的类都继承了该示例,此示例适用于您自己创建的需要NSTimer功能的类……
Martin Cazares 2014年

14

如果要从NSTimer向函数传递参数,则可以采用以下解决方案:

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

在选择器文本(测试器:)中包含冒号,然后将参数输入到userInfo中。

您的函数应将NSTimer作为参数。然后只需提取userInfo即可获取传递的参数。


3
我正在使用NSTimer(0.01,target:self,...)无效,而使用NSTimer.scheduledTimerWithTimeInterval(0.01,..)DID则有效!奇怪,但是感谢@Scooter的回答!
iOS-Coder

1
@ iOS-Coder仅使用初始化程序创建计时器不会将其添加到运行循环中,而会scheduledTimerWith...自动将其添加到当前运行循环中-因此这里完全没有奇怪的行为;)
David Ganster 2015年

1
@David感谢您的建议。我想我的误解应该属于STFW还是RTFA(阅读F ... ing API)类别?
iOS编码器

1
不用担心,没有人可以阅读每个API中每个方法的文档;)
David Ganster,2015年

10

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

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

8

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.1有何不同?当您已经有大约20个答案时,为什么还要添加另一个答案呢?
Fogmeister

swift 4中调用选择器的方式有所不同。swift 4中尝试以下答案,请参见。没有这些,没有编辑就无法工作。请不要标记任何声明为垃圾邮件,同时确保其impotance
Krunal

那么,有什么理由无法编辑现有的,可接受的答案?这将使它实际上有用,而不是在一长串答案的末尾添加。出现“编辑”按钮是有原因的。
Fogmeister

另外,这与Swift 3的哪一部分不同?
Fogmeister

2
您必须将objc标签添加到Swift 4的所有选择器中。这是正确的答案。而且您不应该编辑其他人的答案来改变他们的意思。@Krunal是完全正确的。
Unome

6
// 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
}

如果您也有一个为Swift3或Swift4接受两个或三个参数的示例,那会更好,更富教育意义。谢谢。
nyxee

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

}

5

自从Swift 3.0发布以来,声明一个targetAction合适的代码甚至更加微妙。

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")
        }
    }
}

4

使用时 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)

3

您可以如下所示创建选择器。
1。

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

2。

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

请注意,@ selector语法已消失,并由命名要调用的方法的简单String代替。在一个领域中,我们都可以同意冗长的方式。当然,如果我们声明有一个名为flatButtonPressed的目标方法:我们最好编写一个:

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

为了完整起见,这是flatButtonPressed

func flatButtonPressed(timer: NSTimer) {
}

您是否有“ 请注意@selector语法已消失 ”的任何来源?
winklerrr

3

我发现这些答案中的许多都是有帮助的,但目前还不清楚如何用非按钮的方式来做到这一点。我正在迅速并费劲地向UILabel添加手势识别器,因此在阅读完以上所有内容后,这就是我发现的工作:

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

“选择器”被声明为:

func labelTapped(sender: UILabel) { }

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

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

3

使用#selector将在编译时检查您的代码,以确保要调用的方法确实存在。更好的是,如果该方法不存在,则会出现编译错误: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!")
    }

2

注意在哪里设置触发操作的控件很重要。

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

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")
}

1

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

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

之后,键入func test()。


1

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

Swift 2.2语法之前是:

Selector("foo:")

由于函数名称是Selector作为String参数(“ foo”)传递的,因此无法在编译时检查名称。结果,您收到运行时错误:

unrecognized selector sent to instance

之后Swift 2.2+的语法是:

#selector(foo(_:))

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


0

对于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.


-1

迅捷3

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

函数声明在同一类中:

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

如果目标是自己,那么self选择器中就没有必要。这应该足够了:let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
Mithra Singam
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.