Verwenden von -performSelector: vs. nur das Aufrufen der Methode

116

Ich bin noch ein bisschen neu in Objective-C und frage mich, was der Unterschied zwischen den folgenden beiden Aussagen ist.

[object performSelector:@selector(doSomething)]; 

[object doSomething];
Der Spieler
quelle

Antworten:

191

Grundsätzlich können Sie mit performSelector dynamisch bestimmen, welcher Selektor einen Selektor für das angegebene Objekt aufrufen soll. Mit anderen Worten, der Selektor muss nicht vor der Laufzeit festgelegt werden.

Also, obwohl diese gleichwertig sind:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

Mit dem zweiten Formular können Sie Folgendes tun:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

bevor Sie die Nachricht senden.

ennuikiller
quelle
3
Es ist erwähnenswert, dass Sie das Ergebnis von findTheAppropriateSelectorForTheCurrentSituation () tatsächlich einem Selector zuweisen und dann [anObject performSelector: aSelector] aufrufen würden. @selector erzeugt ein SEL.
Daniel Yankowsky
4
Die Verwendung performSelector:ist etwas, das Sie wahrscheinlich nur tun, wenn Sie eine Zielaktion in Ihrer Klasse implementieren. Die Geschwister performSelectorInBackground:withObject:und performSelectorOnMainThread:withObject:waitUntilDone:sind oft nützlicher. Zum Laichen eines Hintergrundthreads und zum Zurückrufen von Ergebnissen aus diesem Hintergrundthread an den Hauptthread.
PeyloW
2
performSelectorist auch nützlich, um Kompilierungswarnungen zu unterdrücken. Wenn Sie wissen, dass die Methode vorhanden ist (wie nach der Verwendung respondsToSelector), wird Xcode daran gehindert, "möglicherweise nicht zu antworten your_selector" zu sagen . Verwenden Sie es einfach nicht, anstatt die wahre Ursache der Warnung herauszufinden. ;)
Marc
Ich habe in einem anderen Thread auf StackOverflow gelesen, dass die Verwendung von performSelector ein Spiegelbild eines schrecklichen Designs war und jede Menge Daumen hoch hatte. Ich wünschte, ich könnte es wieder finden. Ich suchte bei Google, beschränkte die Ergebnisse auf den Stapelüberlauf und erhielt 18.000 Ergebnisse. Eww.
Logicsaurus Rex
"Reflexion eines schrecklichen Designs" ist zu simpel. Dies war, was wir hatten, bevor Blöcke verfügbar waren, und nicht alle Verwendungen sind damals oder heute schlecht. Obwohl jetzt , dass die Blöcke sind vorhanden, das ist wahrscheinlich eine bessere Wahl für neuen Code , wenn Sie etwas sehr einfach tun.
Ethan
16

Für dieses sehr grundlegende Beispiel in der Frage,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

Es gibt keinen Unterschied, was passieren wird. doSomething wird vom Objekt synchron ausgeführt. Nur "doSomething" ist eine sehr einfache Methode, die nichts zurückgibt und keine Parameter benötigt.

Wäre es etwas komplizierter, wie:

(void)doSomethingWithMyAge:(NSUInteger)age;

Dinge würden kompliziert werden, weil [object doSomethingWithMyAge: 42];

kann mit keiner Variante von "performSelector" mehr aufgerufen werden, da alle Varianten mit Parametern nur Objektparameter akzeptieren.

Der Selektor hier wäre "doSomethingWithMyAge:", aber jeder Versuch dazu

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

wird einfach nicht kompiliert. Das Übergeben einer NSNumber: @ (42) anstelle von 42 würde ebenfalls nicht helfen, da die Methode einen grundlegenden C-Typ erwartet - kein Objekt.

Darüber hinaus gibt es performSelector-Varianten mit bis zu 2 Parametern, nicht mehr. Während Methoden oft viel mehr Parameter haben.

Ich habe herausgefunden, dass obwohl synchrone Varianten von performSelector:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

Immer ein Objekt zurückgeben, ich konnte auch ein einfaches BOOL oder NSUInteger zurückgeben, und es hat funktioniert.

Eine der beiden Hauptanwendungen von performSelector besteht darin, den Namen der Methode, die Sie ausführen möchten, dynamisch zusammenzustellen, wie in einer vorherigen Antwort erläutert. Beispielsweise

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

Die andere Verwendung besteht darin, eine Nachricht asynchron an ein Objekt zu senden, das später im aktuellen Runloop ausgeführt wird. Hierfür gibt es mehrere andere performSelector-Varianten.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(Ja, ich habe sie aus verschiedenen Foundation-Klassenkategorien wie NSThread, NSRunLoop und NSObject gesammelt.)

Jede der Varianten hat ihr eigenes spezielles Verhalten, aber alle haben etwas gemeinsam (zumindest wenn waitUntilDone auf NO gesetzt ist). Der Aufruf "performSelector" würde sofort zurückkehren und die Nachricht an das Objekt wird erst nach einiger Zeit in den aktuellen Runloop gestellt.

Aufgrund der verzögerten Ausführung ist natürlich kein Rückgabewert für die Methode des Selektors verfügbar, daher der Rückgabewert - (void) in all diesen asynchronen Varianten.

Ich hoffe ich habe das irgendwie abgedeckt ...

Motti Shneor
quelle
12

@ennuikiller ist genau richtig. Grundsätzlich sind dynamisch generierte Selektoren nützlich, wenn Sie den Namen der Methode, die Sie beim Kompilieren des Codes aufrufen, nicht kennen (und normalerweise auch nicht kennen können).

Ein wesentlicher Unterschied besteht darin, dass -performSelector:Freunde (einschließlich der Multithread- und verzögerten Varianten ) etwas eingeschränkt sind, da sie für die Verwendung mit Methoden mit 0-2-Parametern ausgelegt sind. Das Aufrufen -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:mit 6 Parametern und das Zurückgeben von NSStringist beispielsweise ziemlich unhandlich und wird von den bereitgestellten Methoden nicht unterstützt.

Quinn Taylor
quelle
5
Dazu müssten Sie ein NSInvocationObjekt verwenden.
Dave DeLong
6
Ein weiterer Unterschied: performSelector:und Freunde nehmen alle Objektargumente, was bedeutet, dass Sie sie nicht zum Aufrufen verwenden können (zum Beispiel) setAlphaValue:, da das Argument ein Float ist.
Chuck
4

Selektoren sind ein bisschen wie Funktionszeiger in anderen Sprachen. Sie verwenden sie, wenn Sie zur Kompilierungszeit nicht wissen, welche Methode Sie zur Laufzeit aufrufen möchten. Ebenso wie Funktionszeiger kapseln sie nur den Verbteil des Aufrufs. Wenn die Methode Parameter enthält, müssen Sie diese ebenfalls übergeben.

Ein NSInvocationdient einem ähnlichen Zweck, außer dass es mehr Informationen zusammenhält. Es enthält nicht nur den Verbteil, sondern auch das Zielobjekt und die Parameter. Dies ist nützlich, wenn Sie eine Methode für ein bestimmtes Objekt mit bestimmten Parametern nicht jetzt, sondern in Zukunft aufrufen möchten. Sie können ein geeignetes erstellen NSInvocationund es später abfeuern.

Daniel Yankowsky
quelle
5
Selektoren sind überhaupt nicht wie ein Funktionszeiger, da Sie einen Funktionszeiger mit Argumenten aufrufen können und ein Selektor verwendet werden kann, um eine bestimmte Methode für jedes Objekt aufzurufen, das sie implementiert. Ein Selektor hat nicht den vollständigen Aufrufkontext wie ein Funktionszeiger.
bbum
1
Selektoren sind nicht dasselbe wie Funktionszeiger, aber ich denke immer noch, dass sie ähnlich sind. Sie repräsentieren Verben. C-Funktionszeiger repräsentieren auch Verben. Beides ist ohne zusätzlichen Kontext nicht nützlich. Selektoren benötigen ein Objekt und Parameter; Funktionszeiger erfordern Parameter (die möglicherweise ein Objekt enthalten, mit dem gearbeitet werden soll). Ich wollte hervorheben, wie sie sich von NSInvocation-Objekten unterscheiden, die den gesamten erforderlichen Kontext enthalten. Vielleicht war mein Vergleich verwirrend. In diesem Fall entschuldige ich mich.
Daniel Yankowsky
1
Selektoren sind keine Funktionszeiger. Nicht annähernd. In Wirklichkeit handelt es sich um einfache C-Zeichenfolgen, die einen "Namen" einer Methode enthalten (im Gegensatz zu "Funktion"). Sie sind nicht einmal Methodensignaturen, da sie die Parametertypen nicht einbetten. Ein Objekt kann mehr als eine Methode für denselben Selektor haben (verschiedene Parametertypen oder verschiedene Rückgabetypen).
Motti Shneor
-7

Es gibt einen weiteren subtilen Unterschied zwischen den beiden.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Hier ist der Auszug aus der Apple-Dokumentation

"performSelector: withObject: afterDelay: Führt den angegebenen Selektor für den aktuellen Thread während des nächsten Run-Loop-Zyklus und nach einer optionalen Verzögerungszeit aus. Da er bis zum nächsten Run-Loop-Zyklus wartet, um den Selector auszuführen, bieten diese Methoden eine automatische Mini-Verzögerung von Der aktuell ausgeführte Code. Mehrere Selektoren in der Warteschlange werden nacheinander in der Reihenfolge ausgeführt, in der sie in die Warteschlange gestellt wurden. "

avi
quelle
1
Ihre Antwort ist sachlich falsch. In der Dokumentation, die Sie zitieren, geht es um etwas performSelector:withObject:afterDelay:, aber die Frage und Ihr Snippet verwenden performSelector:eine völlig andere Methode. Aus den Dokumenten dafür: <quote> Die performSelector:Methode entspricht dem Senden einer aSelectorNachricht direkt an den Empfänger. </
Quote
3
danke Josh für die Klarstellung. Du hast Recht; Ich dachte, performSelector/performSelector:withObject/performSelector:withObject:afterDelayalle verhalten sich gleich, was ein Fehler war.
Avi