Swift 中的 @selector()?

@selector() in Swift?

提问人:Arbitur 提问时间:6/3/2014 最后编辑:Mihriban MinazArbitur 更新时间:2/8/2022 访问量:354585

问:

我正在尝试创建一个 in,但我遇到了一些麻烦。NSTimerSwift

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 选择器 nstimer

评论

11赞 Michael Francis 6/10/2014
selector: test()将调用并将其返回值传递给参数。testselector

答:

963赞 rickster 6/3/2014 #1

Swift 本身并不使用选择器——在 Objective-C 中使用选择器的几种设计模式在 Swift 中的工作方式不同。(例如,在协议类型或 / tests 上使用可选链接,而不是 ,并在可能的情况下使用闭包,而不是为了更好的类型/内存安全性。isasrespondsToSelector:performSelector:

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

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

您可以使用表达式构造 from a Swift 函数类型。Selector#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 编译器检查,因此您只能将表达式用于实际存在且有资格用作选择器的类/方法对(请参阅下面的“选择器可用性”)。根据 Swift 2.2+ 的函数类型命名规则,你也可以自由地将函数引用设置为仅根据需要具体引用。#selector

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

对于传递给表达式的函数引用,有几个额外的注意事项:#selector

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

一般注意事项:

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

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

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

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

你可以在 Using Swift with Cocoa 和 Objective-C 中阅读 Interacting with Objective-C API 中有关选择器的更多信息。

注意:在 Swift 2.2 之前,Selector 符合 StringLiteralConvertible,因此您可能会发现将裸字符串传递给接受选择器的 API 的旧代码。您需要在 Xcode 中运行“转换为当前 Swift 语法”来获取使用 #selector 的语法。

评论

9赞 Arbitur 6/3/2014
放置一个带有函数名称的字符串有效,NSSelectorFromString() 也有效。
7赞 6/4/2014
我想提一下,虽然“与 Objective-C API 交互”在网站上,但它不在“The Swift Programming Language”一书中。
11赞 Daniel Schlaug 7/6/2014
这可能应该提到,如果选择器接受参数,则选择器需要在末尾添加一个“:”。(例如 test() -> “test” & test(this:String) -> “test:”)
2赞 JMFR 10/29/2014
还应该指出的是,Cocoa 框架需要一个 Objective-C 风格的方法名称。如果你的方法接受一个参数,你将需要一个 ':' 如果它需要 2 个参数, ,如果第一个参数被命名,你可能需要一个 ,即 为size:andShape:WithinitWithData:func init(Data data: NSData)
6赞 yo.ian.g 12/14/2014
是否有任何方法可以围绕将“选择器”作为字符串传递来添加验证?IE编译器会在我们拼写错误时警告我们,等等。
96赞 Oscar Swanros 6/3/2014 #2

下面是一个关于如何在 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   
}

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

评论

15赞 Lena Bru 6/5/2014
这太可怕了......有没有“NSStringFromSelector”类型的东西?
13赞 malhal 6/6/2014
不敢相信他们为未经检查的选择器设计,因为 OBJC 有这个
4赞 rickster 8/9/2014
@malcomhall:很方便,但它并不像你想象的那么正式。“未声明的选择器”只是编译器发出的警告,因为始终可以在运行时引入新的选择器。不过,Swift 中可验证/可重构的选择器引用将是一个很好的功能请求@selector
2赞 cynistersix 11/13/2014
这个答案很有帮助,但下面带有@objc的答案更合适。
0赞 levous 8/4/2015
当您将选择器字符串作为变量或参数传入时,您需要使用 Selector() 函数让编译器知道它是一个选择器。谢谢
12赞 Jony T 6/5/2014 #3

选择器是 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")
)
48赞 user3771857 6/24/2014 #4

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

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

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

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

评论

3赞 Yevhen Dubinin 12/1/2014
1. 带冒号的选择器必须接受参数 2.根据 Apple 文档,计时器的动作应采用 NSTimer 参数 3。 关键字不是必需的。因此,在这种情况下,签名必须是Selector@objc func method(timer: NSTimer) {/*code*/}
1赞 Mike Taverne 2/6/2015
@objc为我工作。我不需要在我的方法签名中包含它来调用它。timer: NSTimer
3赞 Daxesh Nagar 7/12/2014 #5

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

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

评论

0赞 winklerrr 3/21/2018
你有“注意@selector语法已经消失”的来源吗?
4赞 Michael Peterson 7/13/2014 #6

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

例如,我发现在设置 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")
}
21赞 Martin Cazares 7/13/2014 #7

以防万一其他人遇到与我在 NSTimer 上遇到的相同问题,其中没有其他答案解决了这个问题,值得一提的是,如果您使用的类不是直接从 NSObject 继承的,也不是在层次结构中继承的(例如,手动创建的 swift 文件),即使指定如下,其他答案也不起作用:

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

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

评论

0赞 eharo2 7/23/2014
这种替代方法的问题在于,您不能更改一个类(假设 ViewController)以继承自 NSObject,因为您需要 ViewController 类实现的东西(例如 viewDidLoad())。知道如何使用 NSTimer 在 ViewController 中调用 Swift 函数吗?...e
1赞 Martin Cazares 7/23/2014
UIViewController 已经继承自 NSObject,SDK 公开的大多数类都继承,此示例适用于您自己创建的需要 NSTimer 功能的类...
5赞 Renish Dadhaniya 7/19/2014 #8
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()

}
15赞 Scooter 8/7/2014 #9

如果要将参数从 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)
}

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

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

评论

3赞 iOS-Coder 7/5/2015
我正在使用 NSTimer(0.01, target: self, ...) 不起作用,而使用 NSTimer.scheduledTimerWithTimeInterval(0.01, ..)工作了吗!?奇怪,但谢谢@Scooter你的回答!
1赞 David Ganster 10/6/2015
@iOS-Coder 只是使用初始值设定器创建计时器不会将其添加到运行循环中,而是自动将其添加到当前运行循环中 - 因此这里根本没有奇怪的行为;)scheduledTimerWith...
1赞 iOS-Coder 10/7/2015
@David感谢您的建议。我想我的误解应该属于 STFW 或 RTFA(阅读 F...ing API) 类别?
1赞 David Ganster 10/7/2015
不用担心,没有人可以期望阅读有关每个 API 中每个方法的文档;)
1赞 Thar Htet 10/14/2014 #10

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

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

之后,键入 func test()。

29赞 pravin salame 10/15/2014 #11

斯威夫特 4.0

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

1.将事件添加到按钮中,例如:

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

功能如下:

@objc func clickedButton(sender: AnyObject) {

}

评论

4赞 Vahid Amiri 1/27/2018
你忘了把前面放在 Swift 4 中需要的。@objcfunc
26赞 Rob Sanders 4/11/2015 #12

对于未来的读者,我发现我遇到了一个问题,并且由于将目标标记为私有而导致的无法识别的选择器发送到实例错误。func

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

评论

14赞 apouche 10/28/2015
不必是公共的,您仍然可以将方法保持私有,但在声明之前添加。例如:然后您可以随心所欲地用作选择器objc@objc private func foo() { ..."foo"
0赞 Sajjon 4/6/2017
它也可以是 ,因此不指定任何访问修饰符。我经常使用这种模式:internal//MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
3赞 cynistersix 2/15/2016 #13

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

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

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

func labelTapped(sender: UILabel) { }

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

let tapRecognizer = UITapGestureRecognizer(
            target: self,
            action: Selector("labelTapped:"))
4赞 sschunara 3/17/2016 #14

使用时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)
37赞 Kyle Clegg 4/7/2016 #15

Swift 2.2+ 和 Swift 3 更新

使用新表达式,这样就不需要使用字符串文字,从而不易出错。供参考:#selector

Selector("keyboardDidHide:")

成为

#selector(keyboardDidHide(_:))

Смотритетакже: Swift Evolution 提案

注意 (Swift 4.0):

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

例:

@objc func something(_ sender: UIButton)

3赞 Swift Developer 5/20/2016 #16

使用 #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!")
    }
7赞 John Lee 6/1/2016 #17
// 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
}

评论

0赞 nyxee 7/20/2017
如果你有一个例子,也用两三个参数来说明 Swift3 或 Swift4,那会更好,更有教育意义。谢谢。
5赞 LukeSideWalker 9/17/2016 #18

由于 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")
        }
    }
}
-2赞 Mustajab Haider 2/28/2017 #19

对于 swift 3

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

函数声明 在同一类中:

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

评论

0赞 Mithra Singam 11/26/2019
如果目标是 self,则不需要在选择器中。这应该足够了:selflet timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
12赞 Krunal 6/9/2017 #20

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 的文档: 选择器表达式

评论

0赞 Fogmeister 6/9/2017
请停止这样做。它对任何人都没有帮助。这与 Swift 3.1 有什么不同?为什么你认为有必要在它已经有大约 20 个答案的情况下添加另一个答案?
2赞 Krunal 6/9/2017
在 Swift 4 中调用选择器是不同的。在 swift 4 中试试这些答案,看看。如果没有编辑,这些都不起作用。请不要将任何声明标记为垃圾邮件,除非确保其无能为力
0赞 Fogmeister 6/9/2017
那么,您有什么理由不能编辑现有的、公认的答案吗?这将使它真正有用,而不是在一长串答案的末尾添加。“编辑”按钮的存在是有原因的。
0赞 Fogmeister 6/9/2017
另外,这其中的哪一部分与 Swift 3 不同?
2赞 Unome 9/26/2017
您必须将 objc 标签添加到 Swift 4 的任何选择器中。这是正确答案。而且你不应该编辑别人的答案来改变他们的含义。@Krunal是完全正确的。
0赞 CrazyPro007 5/20/2018 #21

对于 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.
0赞 Mahesh Chaudhari 11/19/2018 #22

Swift 4 中的选择器:

button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
6赞 yoAlex5 12/8/2019 #23

Objective-C 选择器

选择器标识方法。

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

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

例如

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

selector是来自世界的一个词,你可以使用它来有可能从它允许你在运行时执行一些代码Objective-CSwiftObjective-CSwift

前面的语法是:Swift 2.2

Selector("foo:")

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

unrecognized selector sent to instance

语法之后是:Swift 2.2+

#selector(foo(_:))

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

2赞 Nathan Day 2/8/2022 #24

正如许多人所说,选择器是一种动态调用方法的客观 c 方式,它已延续到 Swift,在某些情况下我们仍然坚持使用它,比如 UIKit,可能是因为它们在 SwiftUI 上工作以替换它,但一些 api 有更像 swift 的版本,例如 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:))

您还应该使用 NOT 作为旧的 Objective-C 名称TimerNSTimerNSTimer