2016-02-26 58 views
57

[备注此问题最初是根据Swift 2.2制定的。它已被修订为Swift 4,涉及两个重要的语言变化:第一个外部方法参数不再被自动抑制,并且选择器必须明确暴露于Objective-C。]如何解决Swift #selector语法中“模糊使用”编译错误?

比方说,我有这两种方法我的课:

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

现在我想用雨燕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?);所以这个问题在现实生活中很容易出现。]

回答

83

[备注这个问题最初是在Swift 2.2下编写的。它已被修订为Swift 4,涉及两种重要的语言更改:第一个外部方法参数不再被自动抑制,并且选择器必须明确暴露于Objective-C。]

您可以通过铸造你的函数引用正确的方法签名:

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

(然而,在我看来,你不应该这样做我认为这种情况是一个错误,揭示了斯威夫特的用于参照的功能语法。不足。我提交了一个bug报告,但无济于事。)


只是为了总结新#selector语法:

这种语法的目的是为了防止全太常见的运行时崩溃(通常为“无法识别的选择”)作为文字提供一个选择时,可能出现串。 #selector()需要函数参考,编译器将检查函数是否真的存在并将解析对您的Objective-C选择器的引用。因此,你不容易犯任何错误。

编辑:。好了,是的,你可以你可以是一个完整的lunkhead和目标设定为不落实#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功能。但就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是一个类的方法,而不是一个实例方法,但它会正确地解决选择,尽管如此,这是重要的。)

+0

我没有安装Xcode beta,但不会'#selector(test())'工作吗? – Sulthan

+2

嗨@Sulthan,很高兴收到你的来信。 - 不,这是解释函数调用。根本没有办法直接提出“没有参数的概念”的概念。这是一个漏洞;他们似乎已经提前完成了这个任务,并没有像往常一样思考... – matt

+0

我刚刚通过邮件讨论“使用参数标签命名函数”。引用是:_Zero参数函数引用仍然需要通过上下文类型信息消除歧义。因此,我认为你找到了正确的解决方案。我也不喜欢这个语法,我更喜欢'test(Void)'这样的东西。不幸的是,函数调用和函数描述符之间存在很多语法冲突。 – Sulthan