一尘不染

如何使用Swift #selector语法解决“模棱两可的使用”编译错误?

swift

[ 注意 此问题最初是在Swift 2.2下提出的。它已针对Swift
4进行了修订,涉及两个重要的语言更改:第一个方法参数external不再被自动抑制,并且选择器必须显式地暴露给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?); 因此这个问题很容易在现实生活中出现。]


阅读 300

收藏
2020-07-07

共1个答案

一尘不染

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

您可以解决这个问题,通过 铸造 你的函数引用正确的方法签名:

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

(但是,我认为您不必这样做。我认为这种情况是一个错误,表明Swift引用函数的语法不足。我提交了错误报告,但无济于事。)


只是总结一下新#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(_:)解析为 第二 个,带有参数的表示法。

  • 带有或不带有其签名的其余部分的功能,再加上名 ,显示 类型 的参数。从而:
        @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是类方法而不是实例方法,但是尽管如此,它将正确地解析为选择器,这很重要。)

2020-07-07