[ 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 #selector
Syntax 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 test
Methode, daher #selector
bezieht sich diese darauf, obwohl ein Parameter verwendet wird und der Parameter #selector
nicht 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 test
Methoden, 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)
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.mp
ist 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 test
eher um eine Klassenmethode als um eine Instanzmethode handelt, aber sie wird trotzdem korrekt in einen Selektor aufgelöst, was alles ist, was zählt.)
as
Notation , um die no-Parameter Variante angeben.let selector = #selector(test as (Void) -> Void)
.test as (Void) -> Void
oder die kürzere Syntaxtest as () -> ()
?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 erhaltenSelector
.#selector(Foo.test as (Foo) -> () -> Void) #selector(Foo.test(_:))
Alternativ können Sie auf die Instanzen einer Instanz verweisen,
Selector
wie in der ursprünglichen Antwort gezeigt.let foo = Foo() #selector(foo.test as () -> Void) #selector(foo.test(_:))
quelle
Foo.xxx
ist 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!In meinem Fall (Xcode 11.3.1) war der Fehler nur bei Verwendung von lldb beim Debuggen. Beim Laufen funktioniert es richtig.
quelle