Lösung
Der Compiler warnt aus einem bestimmten Grund davor. Es ist sehr selten, dass diese Warnung einfach ignoriert wird, und es ist einfach, sie zu umgehen. Hier ist wie:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Oder knapper (obwohl schwer zu lesen und ohne Wache):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Erläuterung
Hier wird der Controller nach dem C-Funktionszeiger für die dem Controller entsprechende Methode gefragt. Alle NSObject
antworten darauf methodForSelector:
, aber Sie können es auch class_getMethodImplementation
in der Objective-C-Laufzeit verwenden (nützlich, wenn Sie nur eine Protokollreferenz haben, wie z id<SomeProto>
. B. ). Diese Funktionszeiger heißen IMP
s und sind einfache typedef
ed Funktionszeiger ( id (*IMP)(id, SEL, ...)
) 1 . Dies kann nahe an der tatsächlichen Methodensignatur der Methode liegen, stimmt jedoch nicht immer genau überein.
Sobald Sie das haben IMP
, müssen Sie es in einen Funktionszeiger umwandeln, der alle Details enthält, die ARC benötigt (einschließlich der beiden impliziten versteckten Argumente self
und _cmd
jedes Objective-C-Methodenaufrufs). Dies wird in der dritten Zeile behandelt (die (void *)
rechte Seite teilt dem Compiler einfach mit, dass Sie wissen, was Sie tun, und keine Warnung zu generieren, da die Zeigertypen nicht übereinstimmen).
Schließlich rufen Sie den Funktionszeiger 2 auf .
Komplexes Beispiel
Wenn der Selektor Argumente akzeptiert oder einen Wert zurückgibt, müssen Sie die Dinge ein wenig ändern:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Begründung für die Warnung
Der Grund für diese Warnung ist, dass die Laufzeit bei ARC wissen muss, was mit dem Ergebnis der von Ihnen aufgerufenen Methode zu tun ist. Das Ergebnis könnte sein , alles: void
, int
, char
, NSString *
, id
, usw. ARC wird normalerweise diese Informationen aus dem Header des Objekttypen mit dem Sie arbeiten. 3
Es gibt wirklich nur 4 Dinge, die ARC für den Rückgabewert berücksichtigen würde: 4
- Ignorieren nicht-Objekttypen (
void
, int
, usw.)
- Objektwert beibehalten und freigeben, wenn er nicht mehr verwendet wird (Standardannahme)
- Geben Sie neue Objektwerte frei, wenn sie nicht mehr verwendet werden (Methoden in der
init
/ copy
-Familie oder zugeordnet mit ns_returns_retained
)
- Nichts tun und davon ausgehen, dass der zurückgegebene Objektwert im lokalen Bereich gültig ist (bis der innerste Release-Pool geleert ist, zugeordnet mit
ns_returns_autoreleased
)
Der Aufruf von methodForSelector:
setzt voraus, dass der Rückgabewert der aufgerufenen Methode ein Objekt ist, behält es jedoch nicht bei. Sie könnten also ein Leck erzeugen, wenn Ihr Objekt wie in # 3 oben freigegeben werden soll (dh die Methode, die Sie aufrufen, gibt ein neues Objekt zurück).
Für Selektoren, die Sie versuchen, diese Rückgabe void
oder andere Nicht-Objekte aufzurufen , können Sie Compiler-Funktionen aktivieren, um die Warnung zu ignorieren. Dies kann jedoch gefährlich sein. Ich habe gesehen, wie Clang einige Iterationen durchlaufen hat, wie Rückgabewerte behandelt werden, die lokalen Variablen nicht zugewiesen sind. Es gibt keinen Grund dafür, dass bei aktiviertem ARC der zurückgegebene Objektwert nicht beibehalten und freigegeben werden kann, methodForSelector:
obwohl Sie ihn nicht verwenden möchten. Aus Sicht des Compilers ist es schließlich ein Objekt. Das heißt, wenn die von Ihnen aufgerufene Methode someMethod
ein Nicht-Objekt (einschließlich void
) zurückgibt, kann dies dazu führen, dass ein Garbage-Pointer-Wert beibehalten / freigegeben wird und abstürzt.
Zusätzliche Argumente
Eine Überlegung ist, dass dies dieselbe Warnung ist, bei der performSelector:withObject:
Sie auf ähnliche Probleme stoßen können, wenn Sie nicht angeben, wie diese Methode Parameter verwendet. ARC ermöglicht das Deklarieren verbrauchter Parameter . Wenn die Methode den Parameter verbraucht, senden Sie wahrscheinlich eine Nachricht an einen Zombie und stürzen ab. Es gibt Möglichkeiten, dies mit Bridged Casting zu umgehen, aber es ist wirklich besser, einfach die IMP
oben beschriebene Methode und den Funktionszeiger zu verwenden. Da verbrauchte Parameter selten ein Problem darstellen, ist es unwahrscheinlich, dass dies auftritt.
Statische Selektoren
Interessanterweise beschwert sich der Compiler nicht über statisch deklarierte Selektoren:
[_controller performSelector:@selector(someMethod)];
Der Grund dafür ist, dass der Compiler während der Kompilierung tatsächlich alle Informationen über den Selektor und das Objekt aufzeichnen kann. Es müssen keine Annahmen über irgendetwas gemacht werden. (Ich habe dies vor einem Jahr überprüft, indem ich mir die Quelle angesehen habe, habe aber momentan keine Referenz.)
Unterdrückung
Bei dem Versuch, an eine Situation zu denken, in der die Unterdrückung dieser Warnung und ein gutes Code-Design erforderlich wären, werde ich leer. Bitte teilen Sie jemandem mit, ob er die Erfahrung gemacht hat, diese Warnung zum Schweigen zu bringen (und die oben genannten Dinge nicht richtig handhaben).
Mehr
Es ist möglich, ein zu erstellen NSMethodInvocation
, um dies auch zu handhaben, aber dies erfordert viel mehr Eingabe und ist auch langsamer, so dass es wenig Grund gibt, dies zu tun.
Geschichte
Als die performSelector:
Methodenfamilie zum ersten Mal zu Objective-C hinzugefügt wurde, existierte ARC nicht. Während der Erstellung von ARC entschied Apple, dass für diese Methoden eine Warnung generiert werden sollte, um Entwickler dazu zu führen, andere Mittel zu verwenden, um explizit zu definieren, wie Speicher beim Senden beliebiger Nachrichten über einen benannten Selektor behandelt werden soll. In Objective-C können Entwickler dies mithilfe von Casts im C-Stil für Rohfunktionszeiger tun.
Mit der Einführung von Swift hat Apple die Methodenfamilie als "von Natur aus unsicher" dokumentiertperformSelector:
und steht Swift nicht zur Verfügung.
Im Laufe der Zeit haben wir diesen Fortschritt gesehen:
- Frühere Versionen von Objective-C ermöglichen
performSelector:
(manuelle Speicherverwaltung)
- Objective-C mit ARC warnt vor der Verwendung von
performSelector:
- Swift hat keinen Zugriff auf
performSelector:
diese Methoden und dokumentiert sie als "von Natur aus unsicher".
Die Idee, Nachrichten basierend auf einem benannten Selektor zu senden, ist jedoch keine "inhärent unsichere" Funktion. Diese Idee wird seit langem erfolgreich in Objective-C sowie in vielen anderen Programmiersprachen eingesetzt.
1 Alle Objective-C - Methoden haben zwei versteckte Argumente, self
und _cmd
das sind implizit hinzugefügt , wenn Sie eine Methode aufrufen.
2 Das Aufrufen einer NULL
Funktion ist in C nicht sicher. Der Schutz, mit dem das Vorhandensein des Controllers überprüft wird, stellt sicher, dass wir ein Objekt haben. Wir wissen daher, dass wir eine IMP
von erhalten werden methodForSelector:
(obwohl dies der Fall sein kann _objc_msgForward
, Eintrag in das Nachrichtenweiterleitungssystem). Grundsätzlich wissen wir, dass wir mit der Wache eine Funktion haben, die wir aufrufen können.
3 Tatsächlich kann es zu falschen Informationen kommen, wenn Sie Objekte als deklarieren id
und nicht alle Header importieren. Es kann zu Abstürzen im Code kommen, die der Compiler für in Ordnung hält. Dies ist sehr selten, könnte aber passieren. Normalerweise erhalten Sie nur eine Warnung, dass nicht bekannt ist, aus welcher der beiden Methodensignaturen Sie auswählen können.
4 Weitere Informationen finden Sie in der ARC-Referenz zu beibehaltenen Rückgabewerten und nicht beibehaltenen Rückgabewerten .