我如何解决"模糊地使用"使用Swift #selector语法编译错误?

时间:2016-02-26 17:43:20

标签: swift selector

[注意这个问题最初是在Swift 2.2下制定的。它已针对Swift 4进行了修订,涉及两个重要的语言更改:第一个方法参数external不再自动被抑制,并且选择器必须显式地暴露给Objective-C。]

我们说我班上有这两种方法:

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

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

let selector = #selector(test) // error

...我收到错误,"模糊地使用test()。"但如果我这样说:

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

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

[注意:这个例子不是人为的。 NSObject有Objective-C copycopy:实例方法,Swift copy()copy(sender:AnyObject?);所以问题很容易在现实生活中出现。]

3 个答案:

答案 0 :(得分:97)

[注意>此答案最初是根据Swift 2.2制定的。它已针对Swift 4进行了修订,涉及两个重要的语言更改:第一个方法参数external不再自动被抑制,并且选择器必须显式地暴露给Objective-C。]

您可以通过投射函数引用到正确的方法签名来解决此问题:

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

(但是,在我看来,你不应该这样做。我认为这种情况是一个错误,揭示Swift提到函数的语法是不合适的。我提交了一个错误报告,但无济于事。)< / p>

总结一下新的#selector语法:

此语法的目的是防止在将选择器作为文字字符串提供时可能出现的非常常见的运行时崩溃(通常是“无法识别的选择器”)。 #selector()接受函数引用,编译器将检查该函数是否确实存在,并将为您解析对Objective-C选择器的引用。因此,你不能轻易犯错。

编辑:好的,是的,你可以。你可以成为一个完整的朋克,并将目标设置为一个不实现#selector指定的操作消息的实例。编译器不会阻止你,你会像过去那样崩溃。叹气......)

函数引用可以以三种形式中的任何一种出现:

  • 该功能的裸名。如果函数是明确的,这就足够了。因此,例如:

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

    只有一个test方法,所以这个#selector引用它,即使它需要一个参数而#selector没有提到参数。在幕后,已解析的Objective-C选择器仍将正确"test:"(带冒号,表示参数)。

  • 该功能的名称以及其签名的其余部分。例如:

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

    我们有两种test方法,所以我们需要区分;符号test(_:)解析为第二个一个,带参数的那个。

  • 包含或不包含其余签名的函数的名称,以及 a cast 以显示参数的 types 。因此:

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

    在这里,我们重载了 test(_:)。重载不能暴露给Objective-C,因为Objective-C不允许重载,因此只暴露其中一个,我们只能为 暴露的那个形成一个选择器,因为选择器是Objective-C功能。但就Swift而言,我们必须消除歧义,并且演员也会这样做。

    (正是这种语言特征被使用 - 在我看来被滥用 - 作为上述答案的基础。)

此外,您可能必须通过告诉它函数所在的类来帮助Swift解析函数引用:

  • 如果该类与此类相同,或者与此类中的超类链相同,则通常不需要进一步的解析(如上例所示);可选地,您可以使用点号表示self(例如#selector(self.test),并且在某些情况下您可能需要这样做。

  • 否则,您使用对实现该方法的实例的引用,使用点符号,如此现实示例中所示(self.mp是MPMusicPlayerController):

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

    ...或者您可以使用带有点符号的的名称:

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

    (这似乎是一个奇怪的符号,因为看起来你说test是一个类方法而不是一个实例方法,但它将被正确地解析为选择器,但这一切都很重要。 )

答案 1 :(得分:1)

我想添加一个消除歧义的方法:从类外部访问实例方法。

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

从类的角度来看,test()方法的完整签名为(Foo) -> () -> Void,您需要指定该完整签名才能获得Selector

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

或者,您可以参考原始答案中所示的实例的Selector

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

答案 2 :(得分:0)

在我的情况下(Xcode 11.3.1),错误仅在调试时使用lldb时出现。运行时,它可以正常工作。