如何使用 Swift # 选择器语法解决“使用不明确”的编译错误?

这个问题最初是在 Swift 2.2下提出的。它已经为 Swift 4进行了修订,涉及到两个重要的语言变化: 第一个外部方法参数不再被自动抑制,并且选择器必须明确暴露于 Objective-C。]

假设我的类中有这两个方法:

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}

现在我想使用 Swift 2.2的新 #selector语法来创建一个与这些方法的 第一 func test()相对应的选择器。我该怎么做?当我尝试这个:

let selector = #selector(test) // error

... 我得到一个错误,“模糊使用 test()。”但如果我说:

let selector = #selector(test(_:)) // ok, but...

... 错误消失了,但我现在引用的是 错误的方法,一个 参数。我想引用一个 没有任何参数。我该怎么做?

[注意: 这个例子不是人造的。NSObject 同时具有 Objective-C copycopy:实例方法、 Swift copy()copy(sender:AnyObject?); 因此问题很容易在现实生活中出现。]

42710 次浏览

[NOTE This answer was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]

You can work around this problem by casting your function reference to the correct method signature:

let selector = #selector(test as () -> Void)

(However, in my opinion, you should not have to do this. I regard this situation as a bug, revealing that Swift's syntax for referring to functions is inadequate. I filed a bug report, but to no avail.)


Just to summarize the new #selector syntax:

The purpose of this syntax is to prevent the all-too-common runtime crashes (typically "unrecognized selector") that can arise when supplying a selector as a literal string. #selector() takes a function reference, and the compiler will check that the function really exists and will resolve the reference to an Objective-C selector for you. Thus, you can't readily make any mistake.

(EDIT: Okay, yes you can. You can be a complete lunkhead and set the target to an instance that doesn't implement the action message specified by the #selector. The compiler won't stop you and you'll crash just like in the good old days. Sigh...)

A function reference can appear in any of three forms:

  • The bare name of the function. This is sufficient if the function is unambiguous. Thus, for example:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
    let selector = #selector(test)
    }
    

    There is only one test method, so this #selector refers to it even though it takes a parameter and the #selector doesn't mention the parameter. The resolved Objective-C selector, behind the scenes, will still correctly be "test:" (with the colon, indicating a parameter).

  • The name of the function along with the rest of its signature. For example:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
    let selector = #selector(test(_:))
    }
    

    We have two test methods, so we need to differentiate; the notation test(_:) resolves to the second one, the one with a parameter.

  • The name of the function with or without the rest of its signature, plus a cast to show the types of the parameters. Thus:

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
    let selector1 = #selector(test as (Int) -> Void)
    // or:
    let selector2 = #selector(test(_:) as (Int) -> Void)
    }
    

    Here, we have overloaded test(_:). The overloading cannot be exposed to Objective-C, because Objective-C doesn't permit overloading, so only one of them is exposed, and we can form a selector only for the one that is exposed, because selectors are an Objective-C feature. But we must still disambiguate as far as Swift is concerned, and the cast does that.

    (It is this linguistic feature that is used — misused, in my opinion — as the basis of the answer above.)

Also, you might have to help Swift resolve the function reference by telling it what class the function is in:

  • If the class is the same as this one, or up the superclass chain from this one, no further resolution is usually needed (as shown in the examples above); optionally, you can say self, with dot-notation (e.g. #selector(self.test), and in some situations you might have to do so.

  • Otherwise, you use either a reference to an instance for which the method is implemented, with dot-notation, as in this real-life example (self.mp is an MPMusicPlayerController):

    let pause = UIBarButtonItem(barButtonSystemItem: .pause,
    target: self.mp, action: #selector(self.mp.pause))
    

    ...or you can use the name of the class, with dot-notation:

    class ClassA : NSObject {
    @objc func test() {}
    }
    class ClassB {
    func makeSelector() {
    let selector = #selector(ClassA.test)
    }
    }
    

    (This seems a curious notation, because it looks like you're saying test is a class method rather than an instance method, but it will be correctly resolved to a selector nonetheless, which is all that matters.)

I want to add a missing disambiguation: accessing an instance method from outside the class.

class Foo {
@objc func test() {}
@objc func test(_ sender: AnyObject?) {}
}

From the class' perspective the full signature of the test() method is (Foo) -> () -> Void, which you will need to specify in order to get the Selector.

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))

Alternatively you can refer to an instance's Selectors as shown in the original answer.

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))

In my case (Xcode 11.3.1) the error was only when using lldb while debugging. When running it works properly.