web-dev-qa-db-fra.com

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

Je suis intéressé à comprendre les circonstances conduisant un développeur à remplacer + initialize ou + load. La documentation indique clairement que ces méthodes sont appelées pour vous par le runtime d'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 d'Apple, MVCNetworking. Leur classe de modèle a une méthode +(void) applicationStartup. Il fait un peu de ménage sur le système de fichiers, lit NSDefaults, etc etc ... et, après avoir essayé de maîtriser les méthodes de classe de NSObject, il semble que ce travail de nettoyage puisse être mis en charge.

J'ai modifié le projet MVCNetworking, en supprimant l'appel dans App Delegate vers + applicationStartup et en mettant les bits de maintenance en + charge ... mon ordinateur n'a pas pris feu, mais cela ne veut pas dire que c'est correct! I J'espère comprendre toutes les subtilités, les pièges et les conneries autour d'une méthode de configuration personnalisée que vous devez appeler plutôt que + charger ou + initialiser.


Pour + charger la documentation dit:

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

Cette phrase est compliquée et difficile à analyser si vous ne connaissez pas le sens exact de tous les mots. Aidez-moi!

  • Que signifie "à la fois chargés dynamiquement et liés statiquement?" Quelque chose peut-il être dynamiquement chargé ET statiquement lié, ou s’excluent-ils mutuellement?

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


En ce qui concerne + initialize, la documentation indique:

l'initialiser n'est invoqué qu'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 des méthodes de chargement.

Je suppose que cela signifie "si vous essayez d’installer la classe ... n’utilisez pas initialize". Ok, d'accord. Quand ou pourquoi devrais-je remplacer initialiser alors?

112
edelaney05

Le message load

Le moteur d'exécution envoie le message load à chaque objet de classe, peu de temps après le chargement de cet objet dans l'espace d'adressage du processus. Pour les classes faisant partie du fichier exécutable du programme, le moteur d'exécution envoie le message load très tôt dans la vie du processus. Pour les classes d'une bibliothèque partagée (chargée dynamiquement), le moteur d'exécution envoie le message de chargement juste après le chargement de la bibliothèque partagée dans l'espace d'adressage du processus.

En outre, le moteur d'exécution n'envoie que load à un objet de classe si cet objet de classe implémente lui-même la méthode 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 load à l'objet de classe Superclass. Il n'envoie pas le message load à l'objet de classe Subclass, même si Subclass hérite la méthode de Superclass.

Le moteur d'exécution envoie le message load à un objet de classe après avoir envoyé le message load à tous les objets de superclasse de la classe (si ces objets superclass implémentent load) et tous les objets de classe dans les bibliothèques partagées auxquelles vous vous connectez. Mais vous ne savez pas encore quelles autres classes de votre propre exécutable ont reçu load.

Chaque classe que votre processus charge dans son espace adresse recevra un message load, si elle implémente la méthode load, que votre processus utilise ou non une autre utilisation de la classe.

Vous pouvez voir comment le moteur d'exécution considère la méthode load comme un cas particulier dans le fichier _class_getLoadMethod de objc-runtime-new.mm , et l’appelle directement de call_class_loads dans objc-loadmethod.mm .

Le moteur d'exécution exécute également la méthode load de chaque catégorie qu'il charge, même si plusieurs catégories de la même classe implémentent 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 initialize

Le moteur d'exécution appelle la méthode initialize sur un objet de classe juste avant d'envoyer le premier message (autre que load ou initialize) à l'objet de classe ou à toute instance de la classe. Ce message est envoyé en utilisant le mécanisme normal. Par conséquent, si votre classe n'implémente pas initialize, mais hérite d'une classe qui le fait, votre classe utilisera alors initialize de sa superclasse. Le moteur d'exécution envoie d'abord le initialize à toutes les superclasses d'une classe (si les superclasses n'ont pas déjà été envoyées 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

Etant donné que 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 initialize, chaque classe de votre processus devrait déjà avoir reçu load (le cas échéant).

La manière canonique de mettre en œuvre 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 ne se réinitialise tout seul lorsqu'il a une sous-classe qui n'implémente pas initialize.

Le moteur d'exécution envoie le message initialize dans le fichier _class_initialize fonction 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 Mike Ash vendredi Q & A sur ce sujet.

181
rob mayoff

Cela signifie que ne remplacez pas +initialize Dans une catégorie, vous casserez probablement quelque chose.

+load Est appelé une fois par classe ou catégorie qui implémente +load, dès que cette classe ou catégorie est chargée. Quand il dit "lié statiquement", cela signifie compilé dans votre application binaire. Les méthodes +load Sur les classes ainsi compilées seront exécutées lors du lancement de votre application, probablement avant son entrée dans main(). Quand il dit "chargé dynamiquement", cela signifie chargé via des bundles de plugins ou un appel à dlopen(). Si vous êtes sur iOS, vous pouvez ignorer ce cas.

+initialize Est appelé la première fois qu'un message est envoyé à la classe, juste avant que ce message ne soit traité. Ceci (évidemment) ne se produit qu'une fois. Si vous remplacez +initialize Dans une catégorie, l'une des trois choses suivantes se produira:

  • l'implémentation de votre catégorie est appelée et l'implémentation de la classe ne
  • l'implémentation de la catégorie de quelqu'un d'autre est appelée; rien de ce que vous avez écrit ne
  • 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 toute méthode dans une catégorie car vous ne savez jamais exactement ce que vous remplacez ou si votre propre remplaçant sera lui-même remplacé par une autre catégorie.

BTW, un autre problème à considérer avec +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 configurer les variables static, vous voudrez vous prémunir contre cela: soit avec dispatch_once(), soit en testant self == [MyClass class].

17
user23743