ChatGPT解决这个技术问题 Extra ChatGPT

Swift 中的@selector()?

我正在尝试在 Swift 中创建一个 NSTimer,但遇到了一些问题。

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

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

我在编辑器中收到错误:

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

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

我试过了:

选择器:测试()

选择器:测试

选择器:选择器(测试())

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

selector: test() 将调用 test 并将其返回值传递给 selector 参数。

C
Community

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 表达式与实际存在且有资格用作选择器的类/方法对一起使用(请参阅下面的“选择器可用性”)。您还可以根据 the Swift 2.2+ rules for function-type naming 自由地将您的函数引用设置为您需要的具体内容。

(这实际上是对 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。还有更多KeyPath stuff in Swift 4,因此请确保您使用的是正确的基于 KeyPath 的 API,而不是选择器(如果合适)。

您可以在将 Swift 与 Cocoa 和 Objective-C 结合使用中的 Interacting with Objective-C APIs 下阅读有关选择器的更多信息。

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


使用函数名称放置一个字符串, NSSelectorFromString() 也可以。
我想提一下,虽然“与 Objective-C API 交互”在网站上,但它不在“Swift 编程语言”一书中。
这可能应该提到,如果选择器需要一个参数,那么它最后需要一个“:”。 (例如 test() -> "test" & test(this:String) -> "test:")
还应该指出的是,Cocoa 框架需要一个 Objective-C 风格的方法名。如果您的方法需要一个参数,您将需要一个 ':' 如果它需要 2 个参数,size:andShape:,如果第一个参数被命名,您可能需要一个 With,即 func init(Data data: NSData)initWithData:
无论如何要围绕将“选择器”作为字符串传递来添加验证?当我们拼写错误等时,IE 编译器会警告我们。
O
Oscar Swanros

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

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


这太可怕了......有“NSStringFromSelector”类型的东西吗?
不敢相信他们是为未经检查的选择器设计的,因为 objc 有这个
@malcomhall:@selector 很方便,但并未像您想象的那样正式执行。 “未声明的选择器”只是来自编译器的警告,因为总是可以在运行时引入新的选择器。不过,Swift 中的可验证/可重构选择器引用将是 a good feature request to make
这个答案很有帮助,但下面@objc 的答案更合适。
当您将选择器字符串作为变量或参数传递时,您需要使用 Selector() 函数让编译器知道它是一个选择器。谢谢
L
Léo Natan

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

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

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

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


1. 带有冒号的选择器必须带一个参数 2. 根据 Apple 文档计时器的操作应该带 NSTimer 参数 3. Selector 关键字不是强制性的。所以在这种情况下签名必须是 @objc func method(timer: NSTimer) {/*code*/}
@objc 为我工作。我不需要在方法签名中包含 timer: NSTimer 即可调用它。
J
Joseph Francis

Swift 2.2+ 和 Swift 3 更新

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

Selector("keyboardDidHide:")

变成

#selector(keyboardDidHide(_:))

另请参阅:Swift Evolution Proposal

注意(斯威夫特 4.0):

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

例子:

@objc func something(_ sender: UIButton)


K
Kevin Singh

斯威夫特 4.0

您创建如下所示的选择器。

1.将事件添加到一个按钮,如:

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

功能如下:

@objc func clickedButton(sender: AnyObject) {

}

您忘记将 @objc 放在 func 之前,这在 Swift 4 中是必需的。
R
Rob Sanders

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

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


它没有必须公开,您仍然可以将方法保持为私有,但在声明之前添加 objc。例如:@objc private func foo() { ... 那么您可以随意使用 "foo" 作为选择器
它也可以是 internal,因此不指定任何访问修饰符。我经常使用这种模式://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
r
royhowie

以防万一其他人遇到与 NSTimer 相同的问题,而其他答案都没有解决该问题,如果您使用的类不是直接继承自 NSObject 或在层次结构深处(例如手动创建的 swift 文件),即使指定如下,其他答案也不会起作用:

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

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


这个替代方案的问题是你不能改变一个类(比如说 ViewController)来继承 NSObject,因为你需要 ViewController 类实现的东西(例如 viewDidLoad())。知道如何使用 NSTimer 在 ViewController 中调用 Swift 函数吗?... e
UIViewController 已经从 NSObject 继承,SDK 公开的大多数类都是如此,这个例子是为您自己创建的需要 NSTimer 功能的类...
S
Scooter

如果您想从 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 以获取传递的参数。


我使用的 NSTimer(0.01, target: self, ...) 不起作用,而使用 NSTimer.scheduledTimerWithTimeInterval(0.01, ..) DID 工作!?奇怪,但感谢@Scooter 的回答!
@iOS-Coder 只是使用初始化程序创建一个计时器并不会将其添加到运行循环中,而 scheduledTimerWith... 会自动将其添加到当前运行循环中 - 所以这里根本没有奇怪的行为;)
@David 感谢您的建议。我想我的误解应该属于 STFW 或 RTFA (Read The F...ing API) 类别?
不用担心,没有人会阅读有关每个 API 中每个方法的文档;)
J
Jony T

选择器是 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")
)

K
Krunal

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 的文档:Selector Expression


请停止这样做。它对任何人都没有帮助。这与 Swift 3.1 有什么不同?当它已经有大约 20 个答案时,为什么你认为有必要添加另一个答案?
在 swift 4 中调用选择器是不同的。在 swift 4 中尝试这些答案并查看。没有这些将在没有编辑的情况下工作。请不要在不确保其重要性的情况下将任何声明标记为垃圾邮件
那么你有什么理由不能编辑现有的、接受的答案吗?它将使它真正有用,而不是添加在一长串答案的末尾。 “编辑”按钮的存在是有原因的。
另外,这与 Swift 3 的哪一部分不同?
您必须将 objc 标签添加到 Swift 4 的任何选择器。这是正确的答案。而且你不应该编辑其他人的答案来改变他们的意思。 @Krunal 是完全正确的。
J
John Lee
// 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 带两个或三个参数的例子,那会更好,更有教育意义。谢谢。
R
Renish Dadhaniya
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()

}

S
Suragch

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

P
Patrick

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

D
Daxesh Nagar

您创建如下所示的选择器。 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 语法已消失”的来源?
c
cynistersix

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

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

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

func labelTapped(sender: UILabel) { }

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

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

S
Swift Developer

使用#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!")
    }

R
Robert Dresler

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

例如,我发现在设置 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")
}

y
yoAlex5

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 的自动完成功能帮助您调用正确的方法


T
Thar Htet

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

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

之后,键入 func test()。


N
Nathan Day

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

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)

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

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

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


C
CrazyPro007

对于斯威夫特 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.

E
Eric Aya

Swift 4 中的选择器:

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

m
moraei

对于迅捷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)