Frage performSelector kann ein Leck verursachen, weil sein Selektor unbekannt ist


Ich erhalte die folgende Warnung vom ARC-Compiler:

"performSelector may cause a leak because its selector is unknown".

Hier ist was ich mache:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Warum bekomme ich diese Warnung? Ich verstehe, dass der Compiler nicht überprüfen kann, ob der Selektor existiert oder nicht, aber warum würde das ein Leck verursachen? Und wie kann ich meinen Code ändern, damit ich diese Warnung nicht mehr bekomme?


1189
2017-08-10 20:23


Ursprung


Antworten:


Lösung

Der Compiler warnt aus diesem Grund. Es ist sehr selten, dass diese Warnung einfach ignoriert wird, und es ist einfach 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 kürzer (obwohl schwer zu lesen und ohne die Wache):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

Erläuterung

Was ist hier los? Sie fragen den Controller nach dem C-Funktionszeiger für die Methode, die dem Controller entspricht. Alle NSObjects antworten auf methodForSelector:, aber Sie können auch verwenden class_getMethodImplementation in der Objective-C-Laufzeit (nützlich, wenn Sie nur eine Protokollreferenz haben, wie id<SomeProto>). Diese Funktionszeiger werden aufgerufen IMPs, und sind einfach typedefed Funktionszeiger (id (*IMP)(id, SEL, ...))1. Dies kann nahe an der tatsächlichen Methodensignatur der Methode liegen, stimmt aber nicht immer genau überein.

Sobald du das hast IMPSie müssen es in einen Funktionszeiger umwandeln, der alle Details enthält, die ARC benötigt (einschließlich der zwei impliziten versteckten Argumente) self und _cmd von jedem Objective-C-Methodenaufruf). Dies wird in der dritten Zeile (der (void *) auf der rechten Seite teilt dem Compiler einfach mit, dass Sie wissen, was Sie tun und keine Warnung generieren, da die Zeigertypen nicht übereinstimmen.

Schließlich rufen Sie den Funktionszeiger auf2.

Komplexes Beispiel

Wenn der Selektor Argumente annimmt oder einen Wert zurückgibt, müssen Sie etwas ä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 Laufzeitumgebung mit ARC wissen muss, was mit dem Ergebnis der von Ihnen aufgerufenen Methode zu tun ist. Das Ergebnis könnte alles sein: void, int, char, NSString *, idusw. ARC bezieht diese Informationen normalerweise aus der Kopfzeile des Objekttyps, mit dem Sie arbeiten.3

Es gibt wirklich nur 4 Dinge, die ARC für den Rückgabewert in Betracht ziehen würde:4

  1. Ignoriere Nicht-Objekttypen (void, int, etc)
  2. Objektwert beibehalten, dann freigeben, wenn es nicht mehr verwendet wird (Standardannahme)
  3. Geben Sie neue Objektwerte frei, wenn sie nicht mehr verwendet werden (Methoden in der init/ copy Familie oder zugeschrieben mit ns_returns_retained)
  4. Führen Sie nichts aus und nehmen Sie an, dass der zurückgegebene Objektwert im lokalen Gültigkeitsbereich gültig ist (bis der innerste Release-Pool geleert wird, mit dem Attribut "attribute") ns_returns_autoreleased)

Der Anruf zu methodForSelector: nimmt an, dass der Rückgabewert der Methode, die er aufruft, ein Objekt ist, aber es nicht zurückhält / freigibt. Sie könnten also ein Leck erstellen, wenn Ihr Objekt wie oben in # 3 freigegeben werden soll (dh die von Ihnen aufgerufene Methode gibt ein neues Objekt zurück).

Bei Selektoren versuchen Sie, diese Rückgabe aufzurufen void oder andere Nicht-Objekte, könnten Sie Compiler-Funktionen aktivieren, um die Warnung zu ignorieren, aber es kann gefährlich sein. Ich habe gesehen, dass Clang ein paar Iterationen der Verarbeitung von Rückgabewerten durchläuft, die nicht lokalen Variablen zugewiesen sind. Es gibt keinen Grund dafür, dass ARC aktiviert wurde, dass der Objektwert, von dem zurückgegeben wird, nicht beibehalten und freigegeben werden kann methodForSelector:obwohl du es nicht benutzen willst. Aus der Sicht des Compilers ist es schließlich ein Objekt. Das heißt, wenn die Methode, die Sie anrufen, someMethod, gibt ein Nichtobjekt zurück (einschließlich void), könnte es passieren, dass ein Garbage-Pointer-Wert beibehalten / freigegeben wird und abstürzt.

Zusätzliche Argumente

Eine Überlegung ist, dass dies die gleiche Warnung ist, mit der auftreten wird performSelector:withObject: und Sie könnten ähnliche Probleme haben, wenn Sie nicht deklarieren, wie diese Methode Parameter verbraucht. ARC ermöglicht das Deklarieren verbrauchte Parameterund wenn die Methode den Parameter verbraucht, wirst du wahrscheinlich eine Nachricht an einen Zombie schicken und abstürzen. Es gibt Möglichkeiten, dies mit Bridged Casting zu umgehen, aber es wäre besser, einfach die IMP und Funktionszeiger-Methode oben. Da verbrauchte Parameter selten ein Problem darstellen, wird dies wahrscheinlich nicht auftreten.

Statische Selektoren

Interessanterweise wird sich der Compiler nicht über statisch deklarierte Selektoren beschweren:

[_controller performSelector:@selector(someMethod)];

Der Grund dafür ist, dass der Compiler tatsächlich während des Kompilierens alle Informationen über den Selektor und das Objekt aufzeichnen kann. Es braucht keine Annahmen über irgendetwas zu machen. (Ich habe dies vor einem Jahr überprüft, indem ich auf die Quelle geschaut habe, aber momentan keinen Bezug habe.)

Unterdrückung

Bei dem Versuch, an eine Situation zu denken, in der die Unterdrückung dieser Warnung notwendig ist und gutes Code-Design, komme ich leer aus. Jemand teilen Sie bitte mit, wenn sie eine Erfahrung gemacht haben, wo diese Warnung zum Schweigen gebracht werden musste (und das oben genannte nicht die Dinge richtig behandelt).

Mehr

Es ist möglich, ein NSMethodInvocation Um dies auch zu handhaben, erfordert dies jedoch viel mehr Eingabe und ist auch langsamer, also gibt es wenig Grund, es zu tun.

Geschichte

Wenn das performSelector: Familie von Methoden wurde zuerst zu Objective-C hinzugefügt, ARC existierte nicht. Bei der Erstellung von ARC entschied Apple, dass für diese Methoden eine Warnung generiert werden sollte, um die Entwickler dazu zu bringen, andere Mittel zu verwenden, um explizit festzulegen, wie der Speicher beim Senden beliebiger Nachrichten über einen benannten Selektor gehandhabt werden soll. In Objective-C können Entwickler dies mithilfe von C-Style-Umwandlungen in Raw-Funktionszeigern tun.

Mit der Einführung von Swift, Apple hat dokumentiert das performSelector: Familie von Methoden als "von Natur aus unsicher" und sie sind für Swift nicht verfügbar.

Im Laufe der Zeit haben wir diesen Fortschritt gesehen:

  1. Frühe Versionen von Objective-C erlauben performSelector: (manuelle Speicherverwaltung)
  2. Objective-C mit ARC warnt vor Verwendung von performSelector:
  3. Swift hat keinen Zugriff auf performSelector: und dokumentiert diese Methoden als "inhärent unsicher"

Die Idee, Nachrichten basierend auf einem benannten Selektor zu senden, ist jedoch keine "inhärent unsichere" Funktion. Diese Idee wird seit langem in Objective-C und vielen anderen Programmiersprachen erfolgreich eingesetzt.


1 Alle Objective-C-Methoden haben zwei versteckte Argumente, self und _cmd Diese werden implizit hinzugefügt, wenn Sie eine Methode aufrufen.

2Anrufen eines NULL Funktion ist in C nicht sicher. Der Wächter, der die Anwesenheit des Controllers überprüft, stellt sicher, dass wir ein Objekt haben. Wir wissen also, dass wir einen bekommen werden IMP von methodForSelector: (obwohl es sein kann _objc_msgForwardEintritt in das Nachrichtenweiterleitungssystem). Im Grunde wissen wir, dass wir eine funktionierende Funktion haben.

3 Tatsächlich ist es möglich, dass es falsche Informationen erhält, wenn Sie Objekte als deklarieren id und du importierst nicht alle Header. Sie könnten mit Abstürzen in Code enden, die der Compiler für gut hält. Dies ist sehr selten, könnte aber passieren. Normalerweise erhalten Sie nur eine Warnung, dass sie nicht wissen, welche von zwei Methodensignaturen ausgewählt werden soll.

4 Siehe die ARC-Referenz auf Retained Rückgabewerte und nicht erhaltene Rückgabewerte für mehr Details.


1142
2017-11-18 21:44



Im LLVM 3.0-Compiler in Xcode 4.2 können Sie die Warnung wie folgt unterdrücken:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

Wenn der Fehler an mehreren Stellen auftritt und Sie das C-Makrosystem verwenden möchten, um die Pragmas auszublenden, können Sie ein Makro definieren, um die Warnung leichter unterdrücken zu können:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Sie können das Makro wie folgt verwenden:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

Wenn Sie das Ergebnis der ausgeführten Nachricht benötigen, können Sie Folgendes tun:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

1170
2017-10-28 19:30



Meine Vermutung darüber ist folgende: Da der Selektor dem Compiler unbekannt ist, kann ARC die richtige Speicherverwaltung nicht erzwingen.

Tatsächlich gibt es Zeiten, in denen die Speicherverwaltung durch eine bestimmte Konvention an den Namen der Methode gebunden ist. Konkret denke ich an Komfortkonstrukteure gegen machen Methoden; Erstere geben per Konvention ein automatisch freigegebenes Objekt zurück; Letzteres ein beibehaltenes Objekt. Die Konvention basiert auf den Namen des Selektors. Wenn der Compiler den Selektor nicht kennt, kann er die richtige 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).


206
2017-08-10 20:43



In deinem Projekt Einstellungen erstellenunter Andere Warnflags (WARNING_CFLAGS), hinzufügen
-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.


119
2017-10-31 13:57



Als Umgehungslösung, bis der Compiler die Warnung überschreiben kann, können Sie die Laufzeitumgebung verwenden

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

Anstatt von

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Du wirst es müssen

#import <objc/message.h>


110
2017-08-16 04:56



Um den Fehler nur in der Datei mit dem Ausführungsselektor zu ignorieren, fügen Sie ein #pragma wie folgt hinzu:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

Dies würde die Warnung in dieser Zeile ignorieren, es aber trotzdem für den Rest Ihres Projekts zulassen.


87
2018-01-18 21:31



Seltsam, aber wahr: Wenn akzeptabel (d. H. Das Ergebnis ist ungültig und es Ihnen nichts ausmacht, den Runloop-Zyklus einmal zuzulassen), fügen Sie eine Verzögerung hinzu, auch wenn diese Null ist:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

Dadurch wird die Warnung entfernt, vermutlich, weil der Compiler dadurch beruhigt wird, dass kein Objekt zurückgegeben werden kann und nicht ordnungsgemäß verwaltet wird.


67
2017-11-11 19:19



Hier ist ein aktualisiertes Makro, das auf der obigen Antwort basiert. Dieser sollte Ihnen erlauben, Ihren Code selbst mit einer return-Anweisung zu umbrechen.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

34
2018-05-07 14:58