Comment puis-je résoudre une “utilisation ambiguë de” l’erreur de compilation avec la syntaxe Swift #selector?

[ NOTE Cette question a été initialement formulée sous Swift 2.2. Il a été révisé pour Swift 4, impliquant deux changements de langue importants: le premier paramètre de méthode external n’est plus automatiquement supprimé et un sélecteur doit être explicitement exposé à Objective-C.]

Disons que j’ai ces deux méthodes dans ma classe:

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

Maintenant, je veux utiliser la nouvelle syntaxe #selector Swift 2.2 pour créer un sélecteur correspondant à la première de ces méthodes, func test() . Comment fait-on ça? Quand j’essaye ceci:

 let selector = #selector(test) // error 

… J’ai une erreur, “Utilisation ambiguë de test() .” Mais si je dis ceci:

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

… l’erreur disparaît, mais je parle maintenant de la mauvaise méthode , celle avec un paramètre. Je veux me référer à celui sans aucun paramètre. Comment fait-on ça?

[Note: l’exemple n’est pas artificiel. NSObject possède à la fois des méthodes de copy et de copy: Objective-C copy: instance, Swift copy() et copy(sender:AnyObject?) ; de sorte que le problème peut facilement se poser dans la vraie vie.]

[ NOTE Cette réponse a été initialement formulée sous Swift 2.2. Il a été révisé pour Swift 4, impliquant deux changements de langue importants: le premier paramètre de méthode external n’est plus automatiquement supprimé et un sélecteur doit être explicitement exposé à Objective-C.]

Vous pouvez contourner ce problème en convertissant votre référence de fonction à la signature de méthode correcte:

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

(Cependant, à mon avis, vous ne devriez pas avoir à faire cela. Je considère cette situation comme un bogue, révélant que la syntaxe de Swift pour faire référence aux fonctions est inadéquate. J’ai déposé un rapport de bogue, mais en vain.)


Juste pour résumer la nouvelle syntaxe de #selector :

Le but de cette syntaxe est d’empêcher les pannes d’exécution trop courantes (généralement “sélecteur non reconnu”) pouvant survenir lors de la fourniture d’un sélecteur en tant que chaîne littérale. #selector() prend une référence de fonction et le compilateur vérifiera que la fonction existe réellement et résoudra la référence à un sélecteur Objective-C pour vous. Ainsi, vous ne pouvez pas faire d’erreur.

( EDIT: OK, oui, vous pouvez. Vous pouvez être un lunkhead complet et définir la cible sur une instance qui #selector pas le message d’action spécifié par le #selector . Le compilateur ne vous arrête pas et vous allez planter comme pour au bon vieux temps. Soupir …)

Une référence de fonction peut apparaître sous l’une des trois formes suivantes:

  • Le nom nu de la fonction. Ceci est suffisant si la fonction est sans ambiguïté. Ainsi, par exemple:

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

    Il n’y a qu’une seule méthode de test , donc ce #selector réfère même s’il prend un paramètre et le #selector ne mentionne pas le paramètre. Le sélecteur Objective-C résolu, dans les coulisses, sera toujours correctement "test:" (avec les deux-points, indiquant un paramètre).

  • Le nom de la fonction avec le rest de sa signature . Par exemple:

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

    Nous avons deux méthodes de test , nous devons donc différencier. le test(_:) notation test(_:) résout le second , celui avec un paramètre.

  • Nom de la fonction avec ou sans le rest de sa signature, plus un cast pour afficher les types de parameters. Ainsi:

     @objc func test(_ integer:Int) {} @nonobjc func test(_ ssortingng:Ssortingng) {} func makeSelector() { let selector1 = #selector(test as (Int) -> Void) // or: let selector2 = #selector(test(_:) as (Int) -> Void) } 

    Ici, nous avons surchargé le test(_:) . La surcharge ne peut pas être exposée à Objective-C, car Objective-C n’autorise pas la surcharge. Par conséquent, un seul d’entre eux est exposé et nous ne pouvons créer un sélecteur que pour celui exposé, car les sélecteurs sont une fonctionnalité Objective-C. . Mais nous devons encore désambiguïser en ce qui concerne Swift, et les acteurs le font.

    (C’est cette caractéristique linguistique qui est utilisée – abusée, à mon avis – comme base de la réponse ci-dessus.]

En outre, vous devrez peut-être aider Swift à résoudre la référence de la fonction en lui indiquant dans quelle classe se trouve la fonction:

  • Si la classe est identique à celle-ci, ou à la chaîne de la super-classe de celle-ci, aucune autre résolution n’est généralement nécessaire (comme indiqué dans les exemples ci-dessus); facultativement, vous pouvez dire vous- self , avec la notation par points (par exemple, #selector(self.test) et dans certaines situations, vous devrez peut-être le faire.

  • Sinon, vous utilisez soit une référence à une instance pour laquelle la méthode est implémentée, soit une notation par points, comme dans cet exemple réel ( self.mp est un MPMusicPlayerController):

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

    … ou vous pouvez utiliser le nom de la classe , avec la notation par points:

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

    (Cela semble une curieuse notation, car on dirait que test est une méthode de classe plutôt qu’une méthode d’instance, mais elle sera correctement résolue en un sélecteur, ce qui est tout ce qui compte.)