Ich erhalte die folgende Warnung vom ARC-Compiler:
"performSelector may cause a leak because its selector is unknown".
Folgendes mache ich:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Warum bekomme ich diese Warnung? Ich verstehe, dass der Compiler nicht überprüfen kann, ob der Selektor vorhanden ist oder nicht, aber warum würde das ein Leck verursachen? Und wie kann ich meinen Code ändern, damit ich diese Warnung nicht mehr erhalte?
ios
objective-c
memory-leaks
automatic-ref-counting
Eduardo Scoz
quelle
quelle
Antworten:
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:
Oder knapper (obwohl schwer zu lesen und ohne Wache):
Erläuterung
Hier wird der Controller nach dem C-Funktionszeiger für die dem Controller entsprechende Methode gefragt. Alle
NSObject
antworten daraufmethodForSelector:
, aber Sie können es auchclass_getMethodImplementation
in der Objective-C-Laufzeit verwenden (nützlich, wenn Sie nur eine Protokollreferenz haben, wie zid<SomeProto>
. B. ). Diese Funktionszeiger heißenIMP
s und sind einfachetypedef
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 Argumenteself
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:
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. 3Es gibt wirklich nur 4 Dinge, die ARC für den Rückgabewert berücksichtigen würde: 4
void
,int
, usw.)init
/copy
-Familie oder zugeordnet mitns_returns_retained
)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 MethodesomeMethod
ein Nicht-Objekt (einschließlichvoid
) 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 dieIMP
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:
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" dokumentiert
performSelector:
und steht Swift nicht zur Verfügung.Im Laufe der Zeit haben wir diesen Fortschritt gesehen:
performSelector:
(manuelle Speicherverwaltung)performSelector:
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 eineIMP
von erhalten werdenmethodForSelector:
(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 .
quelle
performSelector:
Methoden nicht auf diese Weise implementiert werden. Sie haben eine strikte Methodensignatur (Rückgabeid
, ein oder zweiid
Sekunden), sodass keine primitiven Typen behandelt werden müssen.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
bei der Verwendung des neuesten Xcodes aus. (5.1.1) Trotzdem habe ich viel gelernt!void (*func)(id, SEL) = (void *)imp;
nicht kompiliert, ich habe es ersetzt durchvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
zu<…> = (void (*))imp;
oder<…> = (void (*) (id, SEL))imp;
Im LLVM 3.0-Compiler in Xcode 4.2 können Sie die Warnung wie folgt unterdrücken:
Wenn Sie den Fehler an mehreren Stellen erhalten und das C-Makrosystem zum Ausblenden der Pragmas verwenden möchten, können Sie ein Makro definieren, um das Unterdrücken der Warnung zu vereinfachen:
Sie können das Makro folgendermaßen verwenden:
Wenn Sie das Ergebnis der ausgeführten Nachricht benötigen, können Sie Folgendes tun:
quelle
pop
undpush
-Pragmas ist viel sauberer und sicherer.if ([_target respondsToSelector:_selector]) {
oder eine ähnliche Logik verwendet wird.Meine Vermutung dazu lautet: Da der Selektor dem Compiler unbekannt ist, kann ARC keine ordnungsgemäße Speicherverwaltung erzwingen.
In der Tat gibt es Zeiten, in denen die Speicherverwaltung durch eine bestimmte Konvention an den Namen der Methode gebunden ist. Insbesondere denke ich an Convenience-Konstruktoren im Vergleich zu Make- Methoden. die frühere Rückgabe per Konvention ein automatisch freigegebenes Objekt; Letzteres ist ein zurückbehaltenes Objekt. Die Konvention basiert auf den Namen des Selektors. Wenn der Compiler den Selektor nicht kennt, kann er die ordnungsgemäße Speicherverwaltungsregel nicht erzwingen.
Wenn dies korrekt ist, können Sie Ihren Code sicher verwenden, vorausgesetzt, Sie stellen sicher, dass die Speicherverwaltung in Ordnung ist (z. B. dass Ihre Methoden keine von ihnen zugewiesenen Objekte zurückgeben).
quelle
__attribute
jeder Methode explizit eine hinzugefügt werden muss, um die Speicherverwaltung zu erläutern. Es macht es dem Komplizen jedoch auch unmöglich, mit diesem Muster richtig umzugehen (ein Muster, das früher sehr verbreitet war, in den letzten Jahren jedoch durch robustere Muster ersetzt wurde).SEL
und je nach Situation unterschiedliche Selektoren zuweisen?Fügen Sie in Ihrem Projekt Build Settings unter Other Warning Flags (
WARNING_CFLAGS
) hinzu-Wno-arc-performSelector-leaks
Stellen Sie jetzt sicher, dass der von Ihnen aufgerufene Selektor nicht dazu führt, dass Ihr Objekt beibehalten oder kopiert wird.
quelle
Als Problemumgehung, bis der Compiler das Überschreiben der Warnung zulässt, können Sie die Laufzeit verwenden
anstatt
Du musst
quelle
[_controller performSelector:NSSelectorFromString(@"someMethod")];
undobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
sind nicht gleichwertig! Werfen Sie einen Blick auf Method Signature Mismatches und eine große Schwäche in der schwachen Typisierung von Objective-C, die das Problem ausführlich erklärt.Fügen Sie ein #pragma wie folgt hinzu, um den Fehler nur in der Datei mit dem Perform-Selektor zu ignorieren:
Dies würde die Warnung in dieser Zeile ignorieren, sie jedoch für den Rest Ihres Projekts zulassen.
quelle
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Ich weiß, wenn ich eine Warnung ausschalte, schalte ich sie gerne zum frühestmöglichen Zeitpunkt wieder ein, damit ich nicht versehentlich eine weitere unerwartete Warnung vorbeiziehen lasse. Es ist unwahrscheinlich, dass dies ein Problem ist, aber es ist nur meine Praxis, wenn ich eine Warnung ausschalte.#pragma clang diagnostic warning push
bevor Sie Änderungen vornehmen, und#pragma clang diagnostic warning pop
den vorherigen Status wiederherstellen. Nützlich, wenn Sie Lasten ausschalten und nicht viele Pragma-Zeilen in Ihrem Code wieder aktivieren möchten.Seltsam, aber wahr: Wenn akzeptabel (dh das Ergebnis ist ungültig und es macht Ihnen nichts aus, den Runloop-Zyklus einmal zuzulassen), fügen Sie eine Verzögerung hinzu, auch wenn diese Null ist:
Dadurch wird die Warnung entfernt, vermutlich weil dadurch der Compiler beruhigt wird, dass kein Objekt zurückgegeben und irgendwie schlecht verwaltet werden kann.
quelle
Hier ist ein aktualisiertes Makro basierend auf der oben angegebenen Antwort. Dieser sollte es Ihnen ermöglichen, Ihren Code auch mit einer return-Anweisung zu verpacken.
quelle
return
muss nicht im Makro sein;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
funktioniert auch und sieht vernünftiger aus.Dieser Code beinhaltet keine Compiler-Flags oder direkten Laufzeitaufrufe:
NSInvocation
Ermöglicht das Festlegen mehrerer Argumente, sodassperformSelector
dies im Gegensatz dazu bei jeder Methode funktioniert.quelle
Nun, viele Antworten hier, aber da dies etwas anders ist, habe ich ein paar Antworten kombiniert, von denen ich dachte, ich würde sie einfügen. Ich verwende eine NSObject-Kategorie, die überprüft, ob der Selektor ungültig ist, und auch den Compiler unterdrückt Warnung.
quelle
Um der Nachwelt willen habe ich beschlossen, meinen Hut in den Ring zu werfen :)
In letzter Zeit habe ich immer mehr Umstrukturierungen außerhalb des
target
/selector
-Paradigmas zugunsten von Dingen wie Protokollen, Blöcken usw. gesehen. Es gibt jedoch einen Drop-In-Ersatz dafürperformSelector
, den ich jetzt einige Male verwendet habe:Dies scheint ein sauberer, ARC-sicherer und nahezu identischer Ersatz zu sein,
performSelector
ohne zu viel damit zu tun zu habenobjc_msgSend()
.Ich habe jedoch keine Ahnung, ob unter iOS ein Analog verfügbar ist.
quelle
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Ich habe es mir einmal angesehen, aber es ist irgendwie unangenehm, eine UI-bezogene Klasse in der Mitte Ihrer Domain oder Ihres Dienstes zu verwenden, um nur einen dynamischen Anruf zu tätigen. Vielen Dank, dass Sie dies aufgenommen haben!id
from-performSelector:...
to:
sei denn, es ist Null, was es nicht ist. Es geht einfach direkt zum Zielobjekt ohne vorherige Überprüfung. Es gibt also keinen "Overhead". Es ist keine großartige Lösung, aber der Grund, den Sie angeben, ist nicht der Grund. :)Die Antwort von Matt Galloway auf diesen Thread erklärt das Warum:
Es scheint im Allgemeinen sicher zu sein, die Warnung zu unterdrücken, wenn Sie den Rückgabewert ignorieren. Ich bin mir nicht sicher, was die beste Vorgehensweise ist, wenn Sie wirklich ein beibehaltenes Objekt von performSelector erhalten müssen - außer "Tun Sie das nicht".
quelle
@ c-Straße bietet die richtige Verbindung mit Problembeschreibung hier . Unten sehen Sie mein Beispiel, wenn performSelector einen Speicherverlust verursacht.
Die einzige Methode, die in meinem Beispiel einen Speicherverlust verursacht, ist CopyDummyWithLeak. Der Grund ist, dass ARC nicht weiß, dass copySelector ein beibehaltenes Objekt zurückgibt.
Wenn Sie das Memory Leak Tool ausführen, sehen Sie das folgende Bild: ... und in keinem anderen Fall treten Speicherlecks auf:
quelle
Um das Makro von Scott Thompson allgemeiner zu gestalten:
Dann benutze es so:
quelle
Warnungen nicht unterdrücken!
Es gibt nicht weniger als 12 alternative Lösungen zum Basteln mit dem Compiler.
Während Sie zum Zeitpunkt der ersten Implementierung klug sind, können nur wenige Ingenieure auf der Erde in Ihre Fußstapfen treten, und dieser Code wird irgendwann brechen.
Sichere Routen:
Alle diese Lösungen funktionieren mit einer gewissen Abweichung von Ihrer ursprünglichen Absicht. Angenommen, das
param
kann sein,nil
wenn Sie dies wünschen:Sicherer Weg, gleiches konzeptionelles Verhalten:
Sicherer Weg, etwas anderes Verhalten:
(Siehe diese Antwort.)
Verwenden Sie anstelle von einen beliebigen Thread
[NSThread mainThread]
.Gefährliche Wege
Erfordert eine Art Compiler-Stummschaltung, die zwangsläufig unterbrochen wird. Beachten Sie, dass zum gegenwärtigen Zeitpunkt, es tat Pause in Swift .
quelle
performSelectorOnMainThread
ist kein guter Weg, um die Warnung zum Schweigen zu bringen, und hat Nebenwirkungen. (Es löst das Speicherleck nicht.) Das Extra#clang diagnostic ignored
unterdrückt die Warnung explizit auf sehr klare Weise.- (void)
Methode das eigentliche Problem ist.Da Sie ARC verwenden, müssen Sie iOS 4.0 oder höher verwenden. Dies bedeutet, dass Sie Blöcke verwenden können. Wenn Sie sich nicht an den auszuführenden Selektor erinnern, sondern stattdessen einen Block nehmen würden, könnte ARC besser verfolgen, was tatsächlich vor sich geht, und Sie müssten nicht das Risiko eingehen, versehentlich einen Speicherverlust einzuführen.
quelle
self
über einen ivar verwendet haben (z . B.ivar
anstelle vonself->ivar
).Anstatt den Blockansatz zu verwenden, der mir einige Probleme bereitete:
Ich werde NSInvocation wie folgt verwenden:
quelle
Wenn Sie keine Argumente übergeben müssen, können Sie eine einfache Problemumgehung verwenden
valueForKeyPath
. Dies ist sogar an einemClass
Objekt möglich.quelle
Sie können hier auch ein Protokoll verwenden. Erstellen Sie also ein Protokoll wie folgt:
In Ihrer Klasse, die Ihren Selektor aufrufen muss, haben Sie dann eine @ -Eigenschaft.
Wenn Sie
@selector(doSomethingWithObject:)
eine Instanz von MyObject aufrufen müssen, gehen Sie folgendermaßen vor:quelle