Wie behebe ich den Kompilierungsfehler "Mehrdeutige Verwendung von" mit der Swift # selector-Syntax?

79

[ HINWEIS Diese Frage wurde ursprünglich unter Swift 2.2 formuliert. Es wurde für Swift 4 überarbeitet und beinhaltet zwei wichtige Sprachänderungen: Der erste externe Methodenparameter wird nicht mehr automatisch unterdrückt, und ein Selektor muss explizit Objective-C ausgesetzt werden.]

Angenommen, ich habe diese beiden Methoden in meiner Klasse:

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

Jetzt möchte ich die neue #selectorSyntax von Swift 2.2 verwenden , um einen Selektor zu erstellen, der der ersten dieser Methoden entspricht func test(). Wie mache ich es? Wenn ich das versuche:

let selector = #selector(test) // error

... Ich erhalte die Fehlermeldung "Mehrdeutige Verwendung von test()". Aber wenn ich das sage:

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

... der Fehler verschwindet, aber ich beziehe mich jetzt auf die falsche Methode , die mit einem Parameter. Ich möchte auf die ohne Parameter verweisen . Wie mache ich es?

[Hinweis: Das Beispiel ist nicht künstlich. NSObject verfügt sowohl über Objective-C- copyals auch über copy:Instanzmethoden, Swift copy()und copy(sender:AnyObject?); so kann das Problem im wirklichen Leben leicht auftreten.]

matt
quelle

Antworten:

110

[ HINWEIS Diese Antwort wurde ursprünglich unter Swift 2.2 formuliert. Es wurde für Swift 4 überarbeitet und beinhaltet zwei wichtige Sprachänderungen: Der erste externe Methodenparameter wird nicht mehr automatisch unterdrückt, und ein Selektor muss explizit Objective-C ausgesetzt werden.]

Sie können dieses Problem umgehen, indem Sie Ihre Funktionsreferenz auf die richtige Methodensignatur umwandeln:

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

(Meiner Meinung nach sollten Sie dies jedoch nicht tun müssen. Ich betrachte diese Situation als Fehler und stelle fest, dass Swifts Syntax für die Bezugnahme auf Funktionen unzureichend ist. Ich habe einen Fehlerbericht eingereicht, aber ohne Erfolg.)


Um die neue #selectorSyntax zusammenzufassen:

Der Zweck dieser Syntax besteht darin, die allzu häufigen Laufzeitabstürze (normalerweise "nicht erkannter Selektor") zu verhindern, die auftreten können, wenn ein Selektor als Literalzeichenfolge angegeben wird. #selector()Nimmt eine Funktionsreferenz , und der Compiler überprüft, ob die Funktion tatsächlich vorhanden ist, und löst die Referenz auf einen Objective-C-Selektor für Sie auf. Sie können also nicht ohne weiteres einen Fehler machen.

( BEARBEITEN: Okay, ja, das können Sie. Sie können ein kompletter Trottel sein und das Ziel auf eine Instanz setzen, die die vom #selector. Angegebene Aktionsnachricht nicht implementiert . Der Compiler wird Sie nicht aufhalten und Sie stürzen genau wie im ab gute alte Zeiten. Seufz ...)

Eine Funktionsreferenz kann in einer von drei Formen erscheinen:

  • Der bloße Name der Funktion. Dies ist ausreichend, wenn die Funktion eindeutig ist. So zum Beispiel:

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

    Es gibt nur eine testMethode, daher #selectorbezieht sich diese darauf, obwohl ein Parameter verwendet wird und der Parameter #selectornicht erwähnt wird. Der aufgelöste Objective-C-Selektor hinter den Kulissen ist weiterhin korrekt "test:"(wobei der Doppelpunkt einen Parameter angibt).

  • Der Name der Funktion zusammen mit dem Rest ihrer Signatur . Zum Beispiel:

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

    Wir haben zwei testMethoden, also müssen wir unterscheiden; Die Notation wird test(_:)in die zweite aufgelöst , die mit einem Parameter.

  • Der Name der Funktion mit oder ohne den Rest ihrer Signatur sowie eine Umwandlung , um die Typen der Parameter anzuzeigen. So:

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

    Hier haben wir überladen test(_:) . Die Überlastung kann nicht in Objective-C ausgesetzt werden, da Objective-C, nicht zu überlasten erlauben , so dass nur einer von ihnen ausgesetzt ist, und wir können für die, die nur einen Wähler bilden wird ausgesetzt, weil Wähler eine Objective-C - Funktion sind . Aber wir müssen immer noch klarstellen, was Swift betrifft, und die Besetzung tut das.

    (Es ist dieses sprachliche Merkmal, das - meiner Meinung nach missbraucht - als Grundlage für die obige Antwort verwendet wird.)

Möglicherweise müssen Sie Swift auch dabei helfen, die Funktionsreferenz aufzulösen, indem Sie ihm mitteilen, in welcher Klasse sich die Funktion befindet:

  • Wenn die Klasse mit dieser identisch ist oder die Oberklassenkette von dieser entfernt ist, ist normalerweise keine weitere Auflösung erforderlich (wie in den obigen Beispielen gezeigt). Optional können Sie sagen self, mit Punktnotation (z. B. #selector(self.test)und in einigen Situationen müssen Sie dies möglicherweise tun.

  • Andernfalls verwenden Sie entweder einen Verweis auf eine Instanz, für die die Methode implementiert ist, mit Punktnotation, wie in diesem realen Beispiel ( self.mpist ein MPMusicPlayerController):

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

    ... oder Sie können den Namen der Klasse mit Punktnotation verwenden:

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

    (Dies scheint eine merkwürdige Notation zu sein, da es so aussieht, als ob Sie sagen, dass es sich testeher um eine Klassenmethode als um eine Instanzmethode handelt, aber sie wird trotzdem korrekt in einen Selektor aufgelöst, was alles ist, was zählt.)

matt
quelle
2
Hallo @Sulthan, schön von dir zu hören. - Nein, das ist ein Funktionsaufruf. Es gibt einfach keine Möglichkeit, das Konzept "das ohne Parameter" direkt zu notieren. Es ist ein Loch; sie scheinen damit fortgefahren zu sein, ohne es (wie so oft)
matt
4
@ Sulthan Wie ich befürchtet hatte, kam der Fehlerbericht "funktioniert wie beabsichtigt" zurück. Also meine Antwort ist die Antwort: Sie haben sich auf die Verwendung asNotation , um die no-Parameter Variante angeben.
Matt
1
Ein weiteres Highlight dessen, was für eine "erstaunliche" Erfahrung es ist, in Swift zu programmieren.
Leo Natan
5
Beim aktuellen Swift 3 müssen Sie die Argumentliste in Klammern setzen : let selector = #selector(test as (Void) -> Void).
Martin R
1
Vielleicht nicht der beste Ort, aber was wird in Swift 3 die bevorzugte Syntax sein? test as (Void) -> Voidoder die kürzere Syntax test as () -> ()?
Dam
1

Ich möchte eine fehlende Begriffsklärung hinzufügen: Zugriff auf eine Instanzmethode von außerhalb der Klasse.

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

Aus Sicht der Klasse test()lautet die vollständige Signatur der Methode (Foo) -> () -> Void, die Sie angeben müssen, um die zu erhalten Selector.

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

Alternativ können Sie auf die Instanzen einer Instanz verweisen, Selectorwie in der ursprünglichen Antwort gezeigt.

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))
Guy Kogus
quelle
Ja, die Notation Foo.xxxist schon komisch, weil dies keine äußerlichen Klassenmethoden sind. Es scheint also, dass der Compiler Ihnen einen Pass gibt, aber nur, wenn es keine Mehrdeutigkeit gibt. Wenn es Unklarheiten gibt, müssen Sie die Ärmel zurückkrempeln und die längere Notation verwenden, was legal und genau ist, da eine Instanzmethode "heimlich" eine Curry-Klassenmethode ist. Sehr feine Erkennung des verbleibenden Randgehäuses!
Matt
0

In meinem Fall (Xcode 11.3.1) war der Fehler nur bei Verwendung von lldb beim Debuggen. Beim Laufen funktioniert es richtig.

user23
quelle