NSObject + load and + initialize - Co oni robią?

115

Interesuje mnie zrozumienie okoliczności prowadzących programistę do zastąpienia + inicjalizacji lub + załadowania. Dokumentacja jasno pokazuje, że te metody są wywoływane przez środowisko wykonawcze Objective-C, ale to naprawdę wszystko, co wynika z dokumentacji tych metod. :-)

Moja ciekawość wynika z przyjrzenia się przykładowemu kodowi Apple - MVCNetworking. Ich klasa modelu ma +(void) applicationStartupmetodę. Robi pewne porządki w systemie plików, czyta NSDefaults, itp. Itd. I po próbie odczytania metod klas NSObject, wygląda na to, że ta praca porządkowa może być w porządku do załadowania.

Zmodyfikowałem projekt MVCNetworking, usuwając wywołanie w App Delegate do + applicationStartup i umieszczając bity porządkowe w + load ... mój komputer się nie zapalił, ale to nie znaczy, że jest poprawny! Mam nadzieję, że zrozumiem wszelkie subtelności, pułapki i inne rzeczy związane z niestandardową metodą konfiguracji, którą musisz wywołać w porównaniu z + ładowaniem lub + inicjalizacją.


Dokumentacja dla + ładowania mówi:

Komunikat ładowania jest wysyłany do klas i kategorii, które są zarówno ładowane dynamicznie, jak i statycznie połączone, ale tylko wtedy, gdy nowo załadowana klasa lub kategoria implementuje metodę, która może odpowiedzieć.

To zdanie jest niezdarne i trudne do przeanalizowania, jeśli nie znasz dokładnego znaczenia wszystkich słów. Wsparcie!

  • Co to znaczy „zarówno ładowane dynamicznie, jak i połączone statycznie”? Czy coś może być ładowane dynamicznie ORAZ statycznie połączone, czy też wzajemnie się wykluczają?

  • „… nowo załadowana klasa lub kategoria implementuje metodę, która może odpowiedzieć„ Jaka metoda? Jak odpowiedzieć?


Jeśli chodzi o + inicjalizację, w dokumentacji jest napisane:

zainicjuj jest wywoływana tylko raz na klasę. Jeśli chcesz wykonać niezależną inicjalizację dla klasy i kategorii klasy, powinieneś zaimplementować metody ładowania.

Rozumiem, że „jeśli próbujesz skonfigurować klasę… nie używaj inicjalizacji”. Ok dobrze. Kiedy lub dlaczego miałbym wtedy zastąpić inicjalizację?

edelaney05
źródło

Odpowiedzi:

184

loadwiadomość

Środowisko wykonawcze wysyła loadkomunikat do każdego obiektu klasy, wkrótce po załadowaniu obiektu klasy do przestrzeni adresowej procesu. W przypadku klas, które są częścią pliku wykonywalnego programu, środowisko wykonawcze wysyła loadkomunikat na bardzo wczesnym etapie życia procesu. W przypadku klas, które znajdują się we współużytkowanej (ładowanej dynamicznie) bibliotece, środowisko wykonawcze wysyła komunikat ładowania zaraz po załadowaniu biblioteki współużytkowanej do przestrzeni adresowej procesu.

Ponadto środowisko wykonawcze wysyła loaddo obiektu klasy tylko wtedy, gdy ten obiekt klasy sam implementuje loadmetodę. Przykład:

@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

Środowisko wykonawcze wysyła loadkomunikat do Superclassobiektu klasy. To nie nie wysyła loadwiadomość do Subclassobiektu klasy, choć Subclassdziedziczy metodę od Superclass.

Środowisko wykonawcze wysyła loadkomunikat do obiektu klasy po wysłaniu loadkomunikatu do wszystkich obiektów nadklasy klasy (jeśli te obiekty nadklasy są zaimplementowane load) i wszystkich obiektów klas w bibliotekach współdzielonych, do których tworzysz łącze. Ale nie wiesz jeszcze, które inne klasy w twoim własnym pliku wykonywalnym otrzymały load.

Każda klasa, którą twój proces ładuje do swojej przestrzeni adresowej, otrzyma loadkomunikat, jeśli implementuje loadmetodę, niezależnie od tego, czy twój proces używa tej klasy w inny sposób.

Można zobaczyć, jak środowisko wykonawcze wyszukuje loadmetodę jako szczególny przypadek w _class_getLoadMethodod objc-runtime-new.mm, i wzywa go bezpośrednio z call_class_loadsw objc-loadmethod.mm.

Środowisko uruchomieniowe uruchamia również loadmetodę każdej ładowanej kategorii, nawet jeśli kilka kategorii na tym samym implementacji klasy load. To jest niezwykłe. Zwykle, jeśli dwie kategorie definiują tę samą metodę w tej samej klasie, jedna z metod „wygra” i zostanie użyta, a druga nigdy nie zostanie wywołana.

initializeMetoda

Środowisko wykonawcze wywołuje initializemetodę na obiekcie klasy tuż przed wysłaniem pierwszej wiadomości (innej niż loadlub initialize) do obiektu klasy lub jakichkolwiek instancji klasy. Ta wiadomość jest wysyłana przy użyciu normalnego mechanizmu, więc jeśli twoja klasa nie implementuje initialize, ale dziedziczy po klasie, która to robi, wtedy twoja klasa użyje swojej nadklasy initialize. Środowisko wykonawcze wyśle ​​najpierw initializedo wszystkich nadklas klasy (jeśli nadklasy nie zostały jeszcze wysłane initialize).

Przykład:

@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;
}

Ten program wypisuje dwa wiersze wyniku:

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

Ponieważ system wysyła initializemetodę leniwie, klasa nie otrzyma komunikatu, chyba że program faktycznie wyśle ​​komunikaty do klasy (lub podklasy albo instancji klasy lub podklas). Zanim to zrobisz initialize, wszystkie zajęcia w Twoim procesie powinny już być load(jeśli to konieczne).

Kanoniczny sposób implementacji initializejest następujący:

@implementation Someclass

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

Celem tego wzorca jest uniknięcie Someclassponownej inicjalizacji, gdy ma podklasę, która nie jest implementowana initialize.

Środowisko wykonawcze wysyła initializekomunikat w _class_initializefunkcji w formacie objc-initialize.mm. Możesz zobaczyć, że używa objc_msgSenddo wysłania, co jest normalną funkcją wysyłania wiadomości.

Dalsza lektura

Zobacz piątkowe pytania i odpowiedzi Mike'a Asha na ten temat.

rob mayoff
źródło
25
Należy pamiętać, że +loadjest wysyłany oddzielnie dla kategorii; oznacza to, że każda kategoria w klasie może zawierać własną +loadmetodę.
Jonathan Grynspan
1
Należy również zauważyć, że metoda initializezostanie poprawnie wywołana load, jeśli to konieczne, ze względu na loadodwołanie do niezainicjowanej jednostki. Może to (co dziwne, ale rozsądne) prowadzić do initializebiegania wcześniej load! W każdym razie to właśnie zauważyłem. Wydaje się, że jest to sprzeczne z „A do czasu, gdy otrzymujesz initialize, każda klasa w twoim procesie powinna już otrzymać load(jeśli to stosowne)”.
Benjohn
5
Otrzymujesz loadpierwszy. Możesz wtedy otrzymać, initializegdy loadjest jeszcze uruchomiony.
rob mayoff
1
@robmayoff czy nie musimy dodawać wierszy [super initialize] i [super load] wewnątrz odpowiednich metod?
damithH
1
Zwykle jest to zły pomysł, ponieważ środowisko wykonawcze wysłało już obie te wiadomości do wszystkich nadklas, zanim wyśle ​​je do Ciebie.
rob mayoff
17

Oznacza to, że nie zastępuj +initializew kategorii, prawdopodobnie coś zepsujesz.

+loadjest wywoływana raz na klasę lub kategorię, która implementuje +load, zaraz po załadowaniu tej klasy lub kategorii. Kiedy mówi „statycznie połączony”, oznacza to wkompilowanie do pliku binarnego Twojej aplikacji. Te +loadmetody na zajęciach zebranych w ten sposób zostanie wykonane, gdy uruchamia app, prawdopodobnie zanim wejdzie main(). Kiedy mówi „ładowane dynamicznie”, oznacza to ładowanie za pośrednictwem pakietów wtyczek lub wywołania dlopen(). Jeśli używasz iOS, możesz zignorować ten przypadek.

+initializejest wywoływana przy pierwszym wysłaniu wiadomości do klasy, tuż przed jej obsługą. To (oczywiście) zdarza się tylko raz. Jeśli zastąpisz +initializekategorię, nastąpi jedna z trzech rzeczy:

  • Twoja implementacja kategorii zostanie wywołana, a implementacja klasy nie
  • wywoływana jest implementacja innej kategorii; nic, co napisałeś, nie działa
  • Twoja kategoria nie została jeszcze załadowana, a jej implementacja nigdy nie zostanie wywołana.

Dlatego nigdy nie powinieneś zastępować +initializew kategorii - w rzeczywistości próba zastąpienia jakiejkolwiek metody w kategorii jest dość niebezpieczna, ponieważ nigdy nie masz pewności, co zastępujesz, ani czy Twój własny zamiennik sam zostanie zastąpiony przez inną kategorię.

Przy okazji, kolejną kwestią do rozważenia +initializejest to, że jeśli ktoś przejdzie do podklasy, potencjalnie zostaniesz wezwany raz dla Twojej klasy i raz dla każdej podklasy. Jeśli robisz coś takiego, jak konfigurowanie staticzmiennych, będziesz chciał się przed tym ustrzec: za pomocą dispatch_once()lub przez testowanie self == [MyClass class].


źródło