NSObject + load et + initialize – Que font-ils?

Je suis intéressé à comprendre les circonstances qui conduisent un développeur à remplacer + initialiser ou + charger. La documentation indique clairement que ces méthodes sont appelées par le runtime Objective-C, mais c’est tout ce qui ressort clairement de la documentation de ces méthodes. 🙂

Ma curiosité vient de regarder l’exemple de code Apple – MVCNetworking. Leur classe de modèle a une méthode +(void) applicationStartup . Il fait du ménage sur le système de fichiers, lit NSDefaults, etc etc … et, après avoir essayé de brûler les méthodes de classe de NSObject, il semble que ce travail de conciergerie soit correct à mettre dans + load.

J’ai modifié le projet MVCNetworking, en supprimant l’appel dans App Delegate vers + applicationStartup, et en mettant les bits de maintenance dans + load … mon ordinateur n’a pas pris feu, mais cela ne veut pas dire que c’est correct! J’espère avoir une compréhension des subtilités, des pièges et des choses autour d’une méthode d’installation personnalisée que vous devez appeler contre + load ou + initialize.


Pour + documentation de chargement dit:

Le message de chargement est envoyé aux classes et aux catégories chargées dynamicment et liées statiquement, mais uniquement si la classe ou la catégorie nouvellement chargée implémente une méthode capable de répondre.

Cette phrase est kludgey et difficile à parsingr si vous ne connaissez pas la signification précise de tous les mots. Aidez-moi!

  • Qu’entend-on par “à la fois dynamicment chargé et statiquement lié”? Quelque chose peut-il être chargé dynamicment ET statiquement lié, ou sont-ils mutuellement exclusifs?

  • “… la classe ou la catégorie nouvellement chargée implémente une méthode qui peut répondre” Quelle méthode? Répondre comment?


Comme pour + initialiser, la documentation dit:

initialiser il est invoqué seulement une fois par classe. Si vous souhaitez effectuer une initialisation indépendante pour la classe et pour les catégories de la classe, vous devez implémenter les méthodes de chargement.

Je suppose que cela signifie “si vous essayez de configurer la classe … n’utilisez pas l’initialisation.” D’accord, d’accord. Quand ou pourquoi devrais-je annuler l’initialisation alors?

Le message de load

Le runtime envoie le message de load à chaque object de classe, peu après le chargement de l’object de classe dans l’espace adresse du processus. Pour les classes faisant partie du fichier exécutable du programme, le moteur d’exécution envoie le message de load très tôt dans le processus. Pour les classes se trouvant dans une bibliothèque partagée (chargée dynamicment), le moteur d’exécution envoie le message de chargement juste après le chargement de la bibliothèque partagée dans l’espace adresse du processus.

En outre, le runtime envoie uniquement le load à un object de classe si cet object de classe lui-même implémente la méthode de load . Exemple:

 @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 

Le moteur d’exécution envoie le message de load à l’object de classe Superclass . Il n’envoie pas le message de load à l’object de classe Subclass , même si la Subclass hérite de la méthode de Superclass .

Le moteur d’exécution envoie le message de load à un object de classe après qu’il a envoyé le message de load à tous les objects de classe supérieure de la classe (si ces objects implémentent le load ) et tous les objects de classe des bibliothèques partagées auxquelles vous vous connectez. Mais vous ne savez pas quelles autres classes de votre propre exécutable ont encore été load .

Chaque classe que votre processus charge dans son espace adresse recevra un message de load s’il implémente la méthode de load , que votre processus utilise ou non la classe.

Vous pouvez voir comment le runtime recherche la méthode de load comme un cas particulier dans la _class_getLoadMethod de objc-runtime-new.mm et l’appelle directement depuis call_class_loads dans objc-loadmethod.mm .

Le runtime exécute également la méthode de load de chaque catégorie qu’il charge, même si plusieurs catégories de la même classe implémentent la load . C’est inhabituel. Normalement, si deux catégories définissent la même méthode sur la même classe, l’une des méthodes «gagnera» et sera utilisée, et l’autre méthode ne sera jamais appelée.

La méthode d’ initialize

Le moteur d’exécution appelle la méthode initialize sur un object de classe juste avant d’envoyer le premier message (autre que load ou initialize ) à l’object de classe ou à des instances de la classe. Ce message est envoyé en utilisant le mécanisme normal, donc si votre classe n’implémente pas l’ initialize , mais hérite d’une classe qui le fait, alors votre classe utilisera l’ initialize sa super-classe. Le runtime enverra d’abord l’ initialize à toutes les super-classes d’une classe (si les super-classes n’ont pas encore été envoyées à l’ initialize ).

Exemple:

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

Ce programme imprime deux lignes de sortie:

 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 

Comme le système envoie la méthode initialize paresseusement, une classe ne recevra le message que si votre programme envoie réellement des messages à la classe (ou à une sous-classe ou à des instances de la classe ou des sous-classes). Et au moment où vous recevez l’ initialize , chaque classe de votre processus doit déjà avoir reçu le load (le cas échéant).

La manière canonique d’implémenter l’ initialize est la suivante:

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

Le but de ce modèle est d’éviter que Someclass se réinitialise lorsqu’il possède une sous-classe qui Someclass pas l’ initialize .

Le runtime envoie le message d’ initialize dans la fonction objc-initialize.mm dans objc-initialize.mm . Vous pouvez voir qu’il utilise objc_msgSend pour l’envoyer, qui est la fonction d’envoi de message normale.

Lectures complémentaires

Découvrez les questions et réponses du vendredi de Mike Ash sur ce sujet.

Ce que cela signifie, c’est de ne pas écraser +initialize dans une catégorie, vous allez probablement casser quelque chose.

+load est appelée une fois par classe ou catégorie qui implémente +load , dès que cette classe ou cette classe est chargée. Quand il est dit “statiquement lié”, cela signifie compilé dans votre application binary. Les méthodes +load sur les classes ainsi compilées seront exécutées au lancement de votre application, probablement avant qu’elle n’entre dans main() . Quand il est dit “dynamicment chargé”, cela signifie qu’il est chargé via des bundles de plug-ins ou un appel à dlopen() . Si vous êtes sur iOS, vous pouvez ignorer ce cas.

+initialize est appelée la première fois qu’un message est envoyé à la classe, juste avant qu’il ne traite ce message. Ceci (évidemment) ne se produit qu’une fois. Si vous écrasez +initialize dans une catégorie, l’une des trois choses suivantes se produira:

  • votre implémentation de la catégorie est appelée et l’implémentation de la classe ne
  • la mise en œuvre de la catégorie de quelqu’un d’autre est appelée; rien que tu a écrit
  • votre catégorie n’a pas encore été chargée et son implémentation n’est jamais appelée.

C’est pourquoi vous ne devriez jamais remplacer +initialize dans une catégorie – en fait, il est très dangereux d’essayer de remplacer une méthode dans une catégorie car vous n’êtes jamais certain de ce que vous remplacez ou de votre propre remplacement par une autre. Catégorie.

BTW, un autre problème à prendre en compte avec l’ +initialize est que si quelqu’un vous sous-classe, vous serez potentiellement appelé une fois pour votre classe et une fois pour chaque sous-classe. Si vous faites quelque chose comme la configuration de variables static , vous voudrez éviter cela: avec dispatch_once() ou en testant self == [MyClass class] .