Como resolvo o erro de compilation “uso ambíguo” com a syntax Swift #selector?

[ NOTA Esta questão foi originalmente formulada no Swift 2.2. Ele foi revisado para o Swift 4, envolvendo duas importantes mudanças de linguagem: o primeiro parâmetro de método externo não é mais automaticamente suprimido e um seletor deve ser explicitamente exposto a Objective-C.]

Digamos que eu tenha esses dois methods na minha aula:

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

Agora eu quero usar a nova syntax #selector Swift 2.2 para fazer um seletor correspondente ao primeiro desses methods, func test() . Como eu faço isso? Quando eu tento isso:

 let selector = #selector(test) // error 

… recebo um erro “Uso ambíguo do test() “. Mas se eu disser isto:

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

… o erro desaparece, mas agora estou me referindo ao método errado , aquele com um parâmetro. Eu quero me referir ao sem nenhum parâmetro. Como eu faço isso?

[Nota: o exemplo não é artificial. NSObject possui copy e copy: Objective-C copy: methods de instância, copy() Swift copy() e copy(sender:AnyObject?) ; então o problema pode facilmente surgir na vida real.]

[ NOTA Esta resposta foi originalmente formulada no Swift 2.2. Ele foi revisado para o Swift 4, envolvendo duas importantes mudanças de linguagem: o primeiro parâmetro de método externo não é mais automaticamente suprimido e um seletor deve ser explicitamente exposto a Objective-C.]

Você pode contornar esse problema, lançando sua referência de function para a assinatura de método correta:

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

(No entanto, na minha opinião, você não deveria ter que fazer isso. Eu considero essa situação como um bug, revelando que a syntax de Swift para se referir a funções é inadequada. Eu enviei um relatório de bug, mas sem sucesso.)


Apenas para resumir a nova syntax #selector :

O objective dessa syntax é impedir que as falhas de tempo de execução muito comuns (geralmente “seletor não reconhecido”) possam surgir ao fornecer um seletor como uma cadeia literal. #selector() pega uma referência de function , e o compilador irá verificar se a function realmente existe e irá resolver a referência a um seletor de Objective-C para você. Assim, você não pode cometer nenhum erro prontamente.

( EDIT: Ok, sim, você pode. Você pode ser um lunkhead completo e definir o alvo para uma instância que não implementa a mensagem de ação especificada pelo #selector . O compilador não irá pará-lo e você irá travar como nos bons velhos tempos. Suspiro …)

Uma referência de function pode aparecer em qualquer uma das três formas:

  • O nome da function. Isso é suficiente se a function não for ambígua. Assim, por exemplo:

     @objc func test(_ sender:AnyObject?) {} func makeSelector() { let selector = #selector(test) } 

    Existe apenas um método de test , então este #selector refere-se a ele mesmo que seja necessário um parâmetro e o #selector não menciona o parâmetro. O seletor resolvido Objective-C, nos bastidores, ainda será corretamente "test:" (com os dois pontos, indicando um parâmetro).

  • O nome da function junto com o resto de sua assinatura . Por exemplo:

     func test() {} func test(_ sender:AnyObject?) {} func makeSelector() { let selector = #selector(test(_:)) } 

    Nós temos dois methods de test , então precisamos diferenciar; o test(_:) notação test(_:) resolve o segundo , aquele com um parâmetro.

  • O nome da function com ou sem o resto de sua assinatura, mais um casting para mostrar os tipos dos parâmetros. Portanto:

     @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) } 

    Aqui, nós temos o test(_:) sobrecarregado test(_:) . A sobrecarga não pode ser exposta a Objective-C, porque Objective-C não permite sobrecarga, portanto, apenas uma delas é exposta e podemos formar um seletor apenas para aquele que está exposto, porque os seletores são um recurso Objective-C . Mas ainda precisamos desambiguar no que diz respeito a Swift, e o casting faz isso.

    (É essa característica lingüística que é usada – mal utilizada, na minha opinião – como a base da resposta acima).

Além disso, você pode ter que ajudar o Swift a resolver a referência de function dizendo a qual class a function está:

  • Se a class é a mesma que esta, ou a cadeia da superclass desta, nenhuma resolução adicional é normalmente necessária (como mostrado nos exemplos acima); opcionalmente, você pode dizer self , com notação de ponto (por exemplo, #selector(self.test) e, em algumas situações, pode ser necessário fazê-lo.

  • Caso contrário, você usa uma referência a uma instância para a qual o método é implementado, com notação de ponto, como neste exemplo real ( self.mp é um MPMusicPlayerController):

     let pause = UIBarButtonItem(barButtonSystemItem: .pause, target: self.mp, action: #selector(self.mp.pause)) 

    … ou você pode usar o nome da class , com notação de ponto:

     class ClassA : NSObject { @objc func test() {} } class ClassB { func makeSelector() { let selector = #selector(ClassA.test) } } 

    (Isso parece uma notação curiosa, porque parece que você está dizendo que o test é um método de class em vez de um método de instância, mas será resolvido corretamente para um seletor, no entanto, que é tudo o que importa.)