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

mat*_*att 71 selector swift

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

假设我的课程中有这两种方法:

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}
Run Code Online (Sandbox Code Playgroud)

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

let selector = #selector(test) // error
Run Code Online (Sandbox Code Playgroud)

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

let selector = #selector(test(_:)) // ok, but...
Run Code Online (Sandbox Code Playgroud)

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

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

mat*_*att 102

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

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

let selector = #selector(test as () -> Void)
Run Code Online (Sandbox Code Playgroud)

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


只是总结一下新#selector语法:

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

(编辑:好的,是的,你可以.你可以成为一个完整的lunkhead并将目标设置为一个不实现由指定的动作消息的实例#selector.编译器不会阻止你,你会崩溃就像在美好的往日.叹息......)

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

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

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    
    Run Code Online (Sandbox Code Playgroud)

    只有一种test方法,所以#selector即使它采用参数并且#selector未提及参数,也会引用它.在幕后,已解析的Objective-C选择器仍将正确显示"test:"(使用冒号,表示参数).

  • 函数的名称及其余签名.例如:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    
    Run Code Online (Sandbox Code Playgroud)

    我们有两种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)
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在这里,我们已经超载了 test(_:).超载不能接触到的Objective-C,因为Objective-C中不允许超载,所以只有一个被暴露出来,我们可以形成只为一个选择暴露出来,因为选择是一个Objective-C功能.但就斯威夫特而言,我们仍然必须消除歧义,并且演员也这样做.

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

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

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

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

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
        target: self.mp, action: #selector(self.mp.pause))
    
    Run Code Online (Sandbox Code Playgroud)

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

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 使用当前的Swift 3,你必须将参数列表放在括号中:`let selector = #selector(test as(Void) - > Void)`. (5认同)
  • @Sulthan正如我所担心的那样,错误报告回来了"按预期工作".所以我的回答是_the_回答:你_have_使用`as`符号来指定无参数变量. (4认同)
  • 嗨@Sulthan,很高兴收到你的来信. - 不,这是一个函数调用.根本没有办法直接表示"没有参数的那个"的概念.这是一个洞; 他们似乎已经没有考虑到这一切(如此经常)...... (2认同)
  • Xcode 9 提出 `#selector(test as () -> Void)`,效果很好 (2认同)
  • @Stan 正确,“Void -> Void”不再是合法类型。我将编辑以修复;向后兼容性没有问题,因为 `() -> Void` 始终是合法的。 (2认同)