NSObject + carica e + inizializza - cosa fanno?


115

Mi interessa capire le circostanze che portano uno sviluppatore a sovrascrivere + inizializzare o + caricare. La documentazione rende chiaro che questi metodi sono chiamati per te dal runtime Objective-C, ma questo è davvero tutto ciò che è chiaro dalla documentazione di quei metodi. :-)

La mia curiosità deriva dall'osservare il codice di esempio di Apple: MVCNetworking. La loro classe modello ha un +(void) applicationStartupmetodo. Fa un po 'di pulizia sul filesystem, legge NSDefaults, ecc. Ecc ... e, dopo aver provato a raggruppare i metodi di classe di NSObject, sembra che questo lavoro di pulizie potrebbe essere ok per + load.

Ho modificato il progetto MVCNetworking, rimuovendo la chiamata in App Delegate a + applicationStartup e inserendo i bit di pulizia in + load ... il mio computer non ha preso fuoco, ma ciò non significa che sia corretto! Spero di ottenere una comprensione di eventuali sottigliezze, trucchi e quant'altro attorno a un metodo di configurazione personalizzato che devi chiamare rispetto a + load o + inizializzazione.


Per + caricare la documentazione dice:

Il messaggio di caricamento viene inviato a classi e categorie che sono sia caricate dinamicamente che collegate staticamente, ma solo se la classe o la categoria appena caricata implementa un metodo in grado di rispondere.

Questa frase è complicata e difficile da analizzare se non conosci il significato preciso di tutte le parole. Aiuto!

  • Cosa si intende per "sia caricato dinamicamente che collegato staticamente?" Qualcosa può essere caricato dinamicamente E collegato staticamente, o si escludono a vicenda?

  • "... la classe o la categoria appena caricata implementa un metodo che può rispondere" Quale metodo? Rispondi come?


Per quanto riguarda + initialize, la documentazione dice:

inizializzare viene invocato solo una volta per classe. Se si desidera eseguire un'inizializzazione indipendente per la classe e per le categorie della classe, è necessario implementare i metodi di caricamento.

Immagino che questo significhi, "se stai cercando di impostare la classe ... non usare inizializza". Ok bene. Quando o perché dovrei sovrascrivere l'inizializzazione allora?

Risposte:


184

Il loadmessaggio

Il runtime invia il loadmessaggio a ogni oggetto della classe, subito dopo che l'oggetto della classe è stato caricato nello spazio degli indirizzi del processo. Per le classi che fanno parte del file eseguibile del programma, il runtime invia il loadmessaggio all'inizio della vita del processo. Per le classi che si trovano in una libreria condivisa (caricata dinamicamente), il runtime invia il messaggio di caricamento subito dopo che la libreria condivisa è stata caricata nello spazio degli indirizzi del processo.

Inoltre, il runtime invia loada un oggetto classe solo se quell'oggetto classe stesso implementa il loadmetodo. Esempio:

@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

Il runtime invia il loadmessaggio Superclassall'oggetto classe. Essa non inviare il loadmessaggio alla Subclassoggetto di classe, anche se Subclasseredita il metodo da Superclass.

Il runtime invia il loadmessaggio a un oggetto di classe dopo aver inviato il loadmessaggio a tutti gli oggetti di superclasse della classe (se tali oggetti di superclasse implementano load) ea tutti gli oggetti di classe nelle librerie condivise a cui ci si collega. Ma non sai ancora quali altre classi nel tuo eseguibile hanno ricevuto load.

Ogni classe che il tuo processo carica nel suo spazio indirizzi riceverà un loadmessaggio, se implementa il loadmetodo, indipendentemente dal fatto che il tuo processo faccia un altro uso della classe.

Puoi vedere come il runtime cerca il loadmetodo come un caso speciale in _class_getLoadMethodof objc-runtime-new.mme lo chiama direttamente da call_class_loadsin objc-loadmethod.mm.

Il runtime esegue anche il loadmetodo di ogni categoria che carica, anche se implementano più categorie sulla stessa classe load. Questo è insolito. Normalmente, se due categorie definiscono lo stesso metodo sulla stessa classe, uno dei metodi "vincerà" e verrà utilizzato e l'altro metodo non verrà mai chiamato.

Il initializemetodo

Il runtime chiama il initializemetodo su un oggetto classe appena prima di inviare il primo messaggio (diverso da loado initialize) all'oggetto classe oa qualsiasi istanza della classe. Questo messaggio viene inviato utilizzando il meccanismo normale, quindi se la tua classe non implementa initialize, ma eredita da una classe che lo fa, la tua classe utilizzerà la sua superclasse initialize. Il runtime invierà prima il initializea tutte le superclassi di una classe (se le superclassi non sono già state inviate initialize).

Esempio:

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

Questo programma stampa due righe di output:

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

Poiché il sistema invia il initializemetodo pigramente, una classe non riceverà il messaggio a meno che il programma non invii effettivamente messaggi alla classe (o una sottoclasse o istanze della classe o delle sottoclassi). E nel momento in cui ricevi initialize, ogni lezione nel tuo processo dovrebbe aver già ricevuto load(se appropriato).

Il modo canonico per implementare initializeè questo:

@implementation Someclass

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

Lo scopo di questo modello è evitare Someclassdi reinizializzarsi quando ha una sottoclasse che non implementa initialize.

Il runtime invia il initializemessaggio nella _class_initializefunzione in objc-initialize.mm. Puoi vedere che usa objc_msgSendper inviarlo, che è la normale funzione di invio di messaggi.

Ulteriore lettura

Dai un'occhiata alle domande e risposte del venerdì di Mike Ash su questo argomento.


25
Si noti che +loadviene inviato separatamente per le categorie; cioè, ogni categoria di una classe può contenere il proprio +loadmetodo.
Jonathan Grynspan

1
Si noti inoltre che initializeverrà correttamente invocato da un loadmetodo, se necessario, a causa del loadriferimento all'entità non inizializzata. Questo può (stranamente, ma sensibilmente) portare a initializecorrere prima load! È quello che ho osservato, comunque. Questo sembra essere contrario a "E quando ricevi initialize, ogni classe nel tuo processo dovrebbe aver già ricevuto load(se appropriato)."
Benjohn

5
Tu ricevi per loadprimo. Potresti quindi ricevere initializementre loadè ancora in esecuzione.
rob mayoff

1
@robmayoff non abbiamo bisogno di aggiungere le righe [super initialize] e [super load], all'interno dei rispettivi metodi?
damithH

1
Di solito è una cattiva idea, perché il runtime ha già inviato entrambi i messaggi a tutte le tue superclassi prima che te li invii.
Rob Mayoff

17

Ciò significa che non sovrascrivere +initializein una categoria, probabilmente romperai qualcosa.

+loadviene chiamato una volta per classe o categoria che implementa +load, non appena quella classe o categoria viene caricata. Quando dice "collegato staticamente" significa compilato nel binario della tua app. I +loadmetodi sulle classi così compilate verranno eseguiti all'avvio della tua app, probabilmente prima che entri main(). Quando dice "caricato dinamicamente", significa caricato tramite bundle di plugin o una chiamata a dlopen(). Se sei su iOS, puoi ignorare quel caso.

+initializeviene chiamato la prima volta che un messaggio viene inviato alla classe, appena prima che gestisca quel messaggio. Questo (ovviamente) accade solo una volta. Se esegui l'override +initializein una categoria, accadrà una delle tre cose:

  • l'implementazione della tua categoria viene chiamata e l'implementazione della classe no
  • l'implementazione della categoria di qualcun altro viene chiamata; niente di quello che hai scritto fa
  • la tua categoria non è stata ancora caricata e la sua implementazione non viene mai chiamata.

Questo è il motivo per cui non dovresti mai sovrascrivere +initializein una categoria: in effetti è piuttosto pericoloso provare a sostituire qualsiasi metodo in una categoria perché non sei mai sicuro di cosa stai sostituendo o se la tua sostituzione verrà a sua volta sostituita da un'altra categoria.

A proposito, un altro problema da considerare +initializeè che se qualcuno ti sottoclasse, potresti essere chiamato una volta per la tua classe e una volta per ogni sottoclasse. Se stai facendo qualcosa come l'impostazione di staticvariabili, ti consigliamo di proteggerti: con dispatch_once()o testando self == [MyClass class].

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.