如何使用 Swift #selector 语法解决“歧义使用”编译错误?

Posted

技术标签:

【中文标题】如何使用 Swift #selector 语法解决“歧义使用”编译错误?【英文标题】:How do I resolve "ambiguous use of" compile error with Swift #selector syntax? 【发布时间】:2016-06-10 01:25:11 【问题描述】:

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

假设我的班级中有这两种方法:

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

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

let selector = #selector(test) // error

... 我收到一个错误“test() 的使用不明确”。但如果我这样说:

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

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

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

【问题讨论】:

【参考方案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(_:) 解析为 second 一个,即带有参数的那个。

带有或不带有其余签名的函数名称,加上强制转换以显示参数的类型。因此:

@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,用点表示法(例如#selector(self.test),在某些情况下你可能必须这样做。

否则,您可以使用对实现该方法的 instance 的引用,使用点表示法,就像在这个现实生活中的示例中一样(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 是一个类方法而不是一个实例方法,但它仍然会被正确地解析为一个选择器,这才是最重要的。)

【讨论】:

嗨@Sulthan,很高兴收到你的来信。 - 不,这是一个函数调用。根本没有办法直接表示“没有参数的那个”这个概念。这是一个洞;他们似乎一直没有考虑(经常)就这样做了...... @Sulthan 正如我所担心的那样,错误报告回来了“按预期工作”。所以我的答案是the 答案:你必须使用as 表示法来指定无参数变体。 另一个亮点是在 Swift 中编写代码是多么“惊人”的体验。 使用当前的 Swift 3,您必须将参数列表放在括号中:let selector = #selector(test as (Void) -> Void) 也许不是最好的地方,但在 Swift 3 中,首选的语法是什么? test as (Void) -> Void 或更短的语法 test as () -> ()?【参考方案2】:

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

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

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

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

或者,您可以参考实例的Selectors,如原始答案所示。

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

【讨论】:

是的,Foo.xxx 符号已经很奇怪了,因为这些不是外在的类方法。所以看起来编译器给了你一个通行证,但前提是没有歧义。如果有歧义,您必须退缩并使用更长的符号,这是合法且准确的,因为实例方法“秘密地”是柯里化类方法。对剩余边缘情况的非常精细的检测!【参考方案3】:

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

【讨论】:

以上是关于如何使用 Swift #selector 语法解决“歧义使用”编译错误?的主要内容,如果未能解决你的问题,请参考以下文章

Swift: Swift中Selector的变化

UITableView 键盘覆盖UITableViewCell的输入框解决方法(swift)

NSTimer - 如何在Swift中延迟

关于@selector的xcode语法问题

在 Swift 3 中添加观察者和选择器

Swift新手教程系列5-函数+selector在swift中的使用方法