web-dev-qa-db-fra.com

Comment résoudre une erreur de compilation "d'utilisation ambiguë" avec Swift #selector?)

[ NOTE Cette question a été formulée à l'origine 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 De 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

... Je reçois 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 faire référence à celui sans aucun paramètre. Comment fait-on ça?

[Note: l'exemple n'est pas artificiel. NSObject a les deux méthodes d'instance Objective-C copy et copy:, Swift copy() et copy(sender:AnyObject?); le problème peut donc facilement se poser dans la vie réelle.]

70
matt

[ [~ # ~] 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 encastingvotre 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 bug, 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 bug, mais en vain.)


Juste pour résumer la nouvelle syntaxe #selector:

Le but de cette syntaxe est d'empêcher les blocages trop fréquents à l'exécution (généralement "sélecteur non reconnu") pouvant survenir lors de la fourniture d'un sélecteur sous forme de chaîne littérale. #selector() prend unréférence de la 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 facilement faire d'erreur.

( EDIT: OK, vous le pouvez. Vous pouvez être un lunkhead complet et définir la cible sur une instance qui n'implémente pas le message d'action spécifié par le #selector. Le compilateur ne vous arrêtera pas et vous vous planterez comme 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. Cela suffit 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 test, donc #selector y fait référence même si elle prend un paramètre et que le #selector ne mentionne pas le paramètre. Le sélecteur Objective-C résolu, situé dans les coulisses, sera toujours correctement "test:" (avec les deux points, indiquant un paramètre).

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

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

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

  • Le nom de la fonction avec ou sans le reste de sa signature, plus a cast pour afficher letypesdes paramètres . Ainsi:

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

    Ici, nous avonssurchargétest(_:). La surcharge ne peut pas être exposée à Objective-C, car Objective-C ne permet pas la surcharge, de sorte qu'un seul d'entre eux est exposé, et nous ne pouvons former un sélecteur que pour celui/estexpose, car les sélecteurs sont une fonctionnalité d'Objective-C. Mais nous devonsstillsans ambiguïté en ce qui concerne Swift, et le casting le fait.

    (C'est cette caractéristique linguistique qui est utilisée - utilisée à mauvais escient, à mon avis - comme base de la réponse ci-dessus.)

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

  • Si la classe est la même que celle-ci, ou en amont de la chaîne de superclasses à partir de celle-ci, aucune résolution supplémentaire n'est généralement nécessaire (comme indiqué dans les exemples ci-dessus); éventuellement, vous pouvez dire self, avec une 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 à uninstancepour lequel la méthode est implémentée, avec 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 duclass, avec notation par points:

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

    (Cela semble une notation curieuse, car il semblerait que vous disiez que test est une méthode de classe plutôt qu'une méthode d'instance, mais elle sera néanmoins correctement résolue dans un sélecteur, ce qui compte tout.)

98
matt