“我如何解决?”;“的含糊用法;使用Swift#选择器语法编译错误?

“我如何解决?”;“的含糊用法;使用Swift#选择器语法编译错误?,swift,selector,Swift,Selector,[注意该问题最初是在Swift 2.2下提出的。该问题已针对Swift 4进行了修订,涉及两个重要的语言更改:第一个方法参数外部不再自动抑制,选择器必须明确暴露于Objective-C。] 假设我的课堂上有两种方法: @objc func test() {} @objc func test(_ sender:AnyObject?) {} 现在,我想使用Swift 2.2的新的#selector语法来创建与第一个方法func test()相对应的选择器。我该怎么做?当我尝试这个: let sel

[注意该问题最初是在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
copy
copy:
实例方法,又有Swift
copy()
copy(发送方:任何对象?
),因此在现实生活中很容易出现问题。]

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

您可以通过将函数引用强制转换为正确的方法签名来解决此问题:

let selector = #selector(test as () -> Void)
(然而,在我看来,您不应该这样做。我认为这种情况是一个bug,表明Swift引用函数的语法不充分。我提交了一份bug报告,但没有结果。)


总结一下新的
#选择器
语法:

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

编辑:好的,你可以。你可以是一个十足的笨蛋,将目标设置为一个实例,该实例不实现由
#选择器指定的操作消息。
。编译器不会阻止你,你会像过去一样崩溃。唉…)

函数引用可以以三种形式出现:

  • 函数的裸名称。如果函数是明确的,这就足够了。例如:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    
    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    
    只有一个
    测试方法
    ,因此此
    选择器
    引用它,即使它接受一个参数,
    #选择器
    没有提及该参数。在幕后,解析的Objective-C选择器仍然正确地为
    测试:
    (冒号表示一个参数)

  • 函数名及其签名的其余部分。例如:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(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函数所在的类来帮助Swift解析函数引用:

  • 如果该类与该类相同,或者位于该类的超类链的上游,则通常不需要进一步的解析(如上面的示例所示);或者,您可以使用点表示法(例如,
    #选择器(self.test)
    )说
    self
    ,在某些情况下,您可能必须这样做

  • 否则,您可以使用点表示法对实现该方法的实例的引用,如本现实示例中所示(
    self.mp
    是MPMusicLayerController):

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

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    
    (这似乎是一个奇怪的符号,因为它看起来像是说
    test
    是一个类方法而不是一个实例方法,但它将正确地解析为选择器,这才是最重要的。)


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

class Foo {
    @objc func test() {}
    @objc func test(_ sender: AnyObject?) {}
}
从类的角度来看,
test()
方法的完整签名是
(Foo)->()->Void
,您需要指定它才能获得
选择器

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))
或者,您可以参考实例的
选择器
s,如原始答案所示

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

在我的例子(Xcode 11.3.1)中,错误只出现在调试时使用lldb时。当运行它时,它工作正常。

Hi@Sulthan,很高兴听到你的消息。-不,这是一个函数调用。根本无法直接表示“无参数的一个”的概念.这是一个漏洞;他们似乎一路都没有思考就这么做了(经常如此).@Sulthan正如我担心的那样,错误报告“按预期运行”。因此我的答案是:你必须使用
as
符号来指定无参数变量。这是多么“神奇”的另一个亮点使用Swift编码是一种体验。使用当前的Swift 3,您可以