NSObject + laden und + initialisieren - Was machen sie?

115

Ich bin daran interessiert, die Umstände zu verstehen, unter denen ein Entwickler + initialisieren oder + laden überschreibt. Die Dokumentation macht deutlich, dass diese Methoden von der Objective-C-Laufzeit für Sie aufgerufen werden, aber das ist wirklich alles, was aus der Dokumentation dieser Methoden hervorgeht. :-)

Meine Neugier kommt von Apples Beispielcode - MVCNetworking. Ihre Modellklasse hat eine +(void) applicationStartupMethode. Es führt einige Verwaltungsarbeiten im Dateisystem durch, liest NSDefaults usw. usw. und nach dem Versuch, die Klassenmethoden von NSObject zu analysieren, scheint es in Ordnung zu sein, diese Hausmeisterarbeit in + load zu setzen.

Ich habe das MVCNetworking-Projekt geändert, den Aufruf in App Delegate an + applicationStartup entfernt und die Housekeeping-Bits in + load geladen ... mein Computer hat kein Feuer gefangen, aber das bedeutet nicht, dass es korrekt ist! Ich hoffe, ein Verständnis für alle Feinheiten, Fallstricke und Dingsbums einer benutzerdefinierten Setup-Methode zu erlangen, die Sie im Vergleich zu + Laden oder + Initialisieren aufrufen müssen.


Für + load Dokumentation heißt es:

Die Lademeldung wird an Klassen und Kategorien gesendet, die sowohl dynamisch geladen als auch statisch verknüpft sind, jedoch nur, wenn die neu geladene Klasse oder Kategorie eine Methode implementiert, die antworten kann.

Dieser Satz ist kludgey und schwer zu analysieren, wenn Sie nicht die genaue Bedeutung aller Wörter kennen. Hilfe!

  • Was ist mit "sowohl dynamisch geladen als auch statisch verknüpft" gemeint? Kann etwas dynamisch geladen UND statisch verknüpft werden oder schließen sie sich gegenseitig aus?

  • "... die neu geladene Klasse oder Kategorie implementiert eine Methode, die antworten kann" Welche Methode? Antworten wie?


In Bezug auf + initialize heißt es in der Dokumentation:

initialize wird nur einmal pro Klasse aufgerufen. Wenn Sie eine unabhängige Initialisierung für die Klasse und für Kategorien der Klasse durchführen möchten, sollten Sie Lademethoden implementieren.

Ich verstehe das so: "Wenn Sie versuchen, die Klasse einzurichten ... verwenden Sie nicht initialize." Okay gut. Wann oder warum sollte ich dann die Initialisierung überschreiben?

edelaney05
quelle

Antworten:

184

Die loadNachricht

Die Laufzeit sendet die loadNachricht sehr bald nach dem Laden des Klassenobjekts in den Adressraum des Prozesses an jedes Klassenobjekt. Bei Klassen, die Teil der ausführbaren Datei des Programms sind, sendet die Laufzeit die loadNachricht sehr früh in der Lebensdauer des Prozesses. Bei Klassen, die sich in einer gemeinsam genutzten (dynamisch geladenen) Bibliothek befinden, sendet die Laufzeit die Lademeldung unmittelbar nach dem Laden der gemeinsam genutzten Bibliothek in den Adressraum des Prozesses.

Darüber hinaus sendet die Laufzeit nur loadzu einer Klassenobjekt , wenn das Klassenobjekt selbst implementiert das loadVerfahren. Beispiel:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

Die Laufzeit sendet die loadNachricht an das SuperclassKlassenobjekt. Es ist nicht die schicke loadNachricht an das SubclassKlassenobjekt, obwohl Subclasserbt die Methode aus Superclass.

Die Laufzeit sendet die loadNachricht an ein Klassenobjekt, nachdem sie die loadNachricht an alle Superklassenobjekte der Klasse (sofern diese Superklassenobjekte implementiert sind load) und an alle Klassenobjekte in gemeinsam genutzten Bibliotheken gesendet hat, mit denen Sie eine Verknüpfung herstellen. Sie wissen jedoch noch nicht, welche anderen Klassen in Ihrer eigenen ausführbaren Datei empfangen wurden load.

Jede Klasse, die Ihr Prozess in seinen Adressraum lädt, erhält eine loadNachricht, wenn die loadMethode implementiert wird, unabhängig davon, ob Ihr Prozess die Klasse anderweitig verwendet.

Sie können sehen, wie die Laufzeit die loadMethode als Sonderfall im _class_getLoadMethodvon objc-runtime-new.mmnachschlägt und sie direkt von call_class_loadsin aufruft objc-loadmethod.mm.

Die Laufzeit führt auch die loadMethode jeder Kategorie aus, die sie lädt, selbst wenn mehrere Kategorien in derselben Klasse implementiert sind load. Das ist ungewöhnlich. Wenn zwei Kategorien dieselbe Methode für dieselbe Klasse definieren, wird normalerweise eine der Methoden „gewinnen“ und verwendet, und die andere Methode wird niemals aufgerufen.

Die initializeMethode

Die Laufzeit ruft die initializeMethode für ein Klassenobjekt auf, bevor die erste Nachricht (außer loadoder initialize) an das Klassenobjekt oder an Instanzen der Klasse gesendet wird . Diese Nachricht wird mit dem normalen Mechanismus gesendet. Wenn Ihre Klasse also nicht implementiert initialize, sondern von einer Klasse erbt, die dies tut, verwendet Ihre Klasse die Oberklassen initialize. Die Laufzeit sendet die zuerst initializean alle Superklassen einer Klasse (falls die Superklassen noch nicht gesendet wurden initialize).

Beispiel:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

Dieses Programm druckt zwei Ausgabezeilen:

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

Da das System die initializeMethode träge sendet , empfängt eine Klasse die Nachricht nur, wenn Ihr Programm tatsächlich Nachrichten an die Klasse (oder eine Unterklasse oder Instanzen der Klasse oder Unterklassen) sendet. Und bis Sie erhalten initialize, sollte jede Klasse in Ihrem Prozess bereits erhalten haben load(falls zutreffend).

Die kanonische Art der Implementierung initializeist folgende:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

Der Zweck dieses Musters besteht darin, zu vermeiden, Someclassdass es sich selbst neu initialisiert, wenn es eine Unterklasse hat, die nicht implementiert wird initialize.

Die Laufzeit sendet die initializeNachricht in der _class_initializeFunktion in objc-initialize.mm. Sie können sehen, dass es objc_msgSendzum Senden verwendet wird. Dies ist die normale Funktion zum Senden von Nachrichten.

Weiterführende Literatur

Lesen Sie die Fragen und Antworten von Mike Ash am Freitag zu diesem Thema.

Rob Mayoff
quelle
25
Sie sollten beachten, dass dies +loadfür Kategorien separat gesendet wird. Das heißt, jede Kategorie in einer Klasse kann eine eigene +loadMethode enthalten.
Jonathan Grynspan
1
Beachten Sie auch, dass initializedies von einer loadMethode bei Bedarf korrekt aufgerufen wird , da loadauf die nicht initialisierte Entität verwiesen wird. Dies kann (seltsamerweise, aber vernünftig) dazu führen, dass initializeman vorher läuft load! Das habe ich jedenfalls beobachtet. Dies scheint im Widerspruch zu "Und bis Sie erhalten initialize, sollte jede Klasse in Ihrem Prozess bereits erhalten haben load(falls zutreffend)."
Benjohn
5
Sie erhalten loadzuerst. Sie können dann empfangen, initializewährend loadnoch läuft.
Rob Mayoff
1
@robmayoff Müssen wir in den jeweiligen Methoden nicht die Zeilen [super initialize] und [super load] hinzufügen?
DamithH
1
Das ist normalerweise eine schlechte Idee, da die Laufzeit beide Nachrichten bereits an alle Ihre Superklassen gesendet hat, bevor sie an Sie gesendet werden.
Rob Mayoff
17

Was es bedeutet, ist, +initializein einer Kategorie nicht zu überschreiben , Sie werden wahrscheinlich etwas kaputt machen.

+loadwird einmal pro Klasse oder Kategorie aufgerufen, die implementiert wird +load, sobald diese Klasse oder Kategorie geladen wird. Wenn "statisch verknüpft" angezeigt wird, bedeutet dies, dass es in Ihre App-Binärdatei kompiliert wurde. Die +loadso kompilierten Methoden für Klassen werden beim Start Ihrer App ausgeführt, wahrscheinlich bevor sie eingegeben wird main(). Wenn "dynamisch geladen" steht, bedeutet dies, dass es über Plugin-Bundles oder einen Aufruf von geladen wird dlopen(). Wenn Sie mit iOS arbeiten, können Sie diesen Fall ignorieren.

+initializewird aufgerufen, wenn eine Nachricht zum ersten Mal an die Klasse gesendet wird, kurz bevor sie diese Nachricht verarbeitet. Dies passiert (offensichtlich) nur einmal. Wenn Sie +initializein einer Kategorie überschreiben , geschieht eines von drei Dingen:

  • Ihre Kategorieimplementierung wird aufgerufen und die Implementierung der Klasse nicht
  • Die Kategorieimplementierung eines anderen wird aufgerufen. nichts, was du geschrieben hast, tut es
  • Ihre Kategorie wurde noch nicht geladen und ihre Implementierung wird nie aufgerufen.

Aus diesem Grund sollten Sie +initializeeine Kategorie niemals überschreiben. Tatsächlich ist es ziemlich gefährlich, eine Methode in einer Kategorie zu ersetzen, da Sie nie sicher sind, was Sie ersetzen oder ob Ihr eigener Ersatz selbst durch eine andere Kategorie ersetzt wird.

Übrigens, ein weiteres Problem, das Sie berücksichtigen sollten, +initializeist, dass Sie möglicherweise einmal für Ihre Klasse und einmal für jede Unterklasse aufgerufen werden, wenn jemand Sie unterordnet. Wenn Sie beispielsweise staticVariablen einrichten, sollten Sie sich davor schützen: entweder mit dispatch_once()oder durch Testen self == [MyClass class].


quelle