Au coeur de l’environnement d’exécution d’Objective-C - Partie 2
Note de l’éditeur — Dans son précédent article, Ezra Epstein nous a montré comment accéder aux fonctionnalités basiques de l’environnement d’exécution d’Objective-C. Dans l’article suivant, il va un peu plus loin, et nous révèle la manière dont l’environnement d’exécution a été implémenté; ce qui nous ménera à une meilleure compréhension des catégories et qui révèle aussi des détails qui pourraient ne pas être présents dans les fichiers entêtes (.h). L’aboutissement de cette petite incursion sera l’étude de RuntimeBrowser, un outil qui permet de naviguer dans les classes à la JavaBrowser. L’auteur l’a trouvé très utile et pense qu’il pourrait vous l’être aussi.
Plus profond, au coeur de l’environnement d’exécution
L’environnement d’exécution d’Objective-C est écrit en C. En fait, la version orginelle d’Objective-C avait été implémentée comme un pré-processeur du compilateur C. Pour rentrer à “l’intérieur” de l’environnement d’exécution d’Objective-C, nous utiliserons des fonctions C qui permettent d’accéder aux structures de données C. La structure principale C est struct objc_class* (connu aussi sous le nom de Class).
Pour récupérer les définitions de fonctions et de struct, nous allons inclure les fichiers entêtes objc. Ils devraient être déjà présents sur votre système dans /usr/include/objc (ou System/Developer/Headers/objc). D’ailleurs, vous n’avez pas besoin de Mac OS X. Le code source et les fichiers entêtes de l’environnement d’exécution sont disponibles dans le projet ‘Open Source’ Darwin : objc4-217.tar.gz. (Vous devez vous enregistrer pour pouvoir télécharger les sources, mais c’est une procédure gratuite).
Dans le précédent article, nous avons récupéré une classe à partir d’un nom (NSString*). Et si nous voulions lister toutes les classes chargées dans l’environnement d’exécution ? Dégainez votre éditeur de choix (j’ai utilisé ProjectBuilder, et j’ai commencé avec un projet “Tool”) et vérifiez que les fichiers d’entête d’objc sont dans votre chemin d’inclusion (ils devraient l’être par défaut).
#import <stdio.h>
#import <objc/objc-runtime.h>
#import <objc/hashtable.h>
void showIvars(Class klass)
{
/* le code est ci-dessous */
}
void showMethodGroups(Class klass, char mType)
{
/* le code est ci-dessous */
}
int main (int argc, const char *argv[]) {
NXHashTable * class_hash = objc_getClasses(); // PAS dans une application multi-threadee***.
NXHashState state = NXInitHashState(class_hash);
struct objc_class * klass;
while (NXNextHashState(class_hash, &state, (void **)&klass))
{
printf ("%s\n", klass->name);
showIvars(klass);
showMethodGroups(klass, '-'); // methodes d'instance
showMethodGroups(klass->isa, '+'); // methodes de classe
}
return 0;
}
Veuillez Noter — Le code de l’environnement d’exécution a assez changé depuis Mac OS X. Il utilise maintenant des verrous pour permettre les accès multi-threadés. Le code ci-dessus fonctionne sur un grand nombre de systèmes déployés. Pour de plus amples détails sur la manière dont fonctionnent les nouvelles fonctions, reportez-vous au commentaire sur objc_getClassList() dans objc-runtime.h ou reportez-vous au code de AllClasses.m dans RuntimeBrowser.
Compilez et lancez le. Puisque showIvars() et showMethodGroups() sont seulement des déclarations — que nous remplirons plus tard — vous n’aurez que des noms de classes sans d’autres détails. Les classes que vous allez voir dépendent des Frameworks (ou d’autres binaires ObjC) avec lesquels vous êtes liés. Par défaut, en utilisant ProjectBuilder, vous verrez toutes les classes de la Foundation.framework. Ce qui inclut les classes cachées, non-documentées et les sous-classes des classes en grappes telles que NSString !
Jetez un coup d’oeil à objc/objc.h. Vous verrez, entre autres :
typedef struct objc_class *Class;
Ouais, une Class est un pointeur sur struct objc_class comme annoncé. Une fois que nous avons une classe, nous pouvons accéder aux informations de l’environnement d’exécution à partir de cette struct. Tant que nous y sommes, prenons un moment pour jeter un coup d’oeil à la définition du type id (le pointeur d’objet universel d’Objective-C) défini également dans objc/objc.h :
typedef struct objc_object {
Class isa;
} *id;
Un objet de type id est un pointeur sur un struct qui contient un seul élément : un pointeur isa vers son struct objc_class dans l’environnement d’exécution. C’est tout.
Aussi surprenant que cela puisse paraître, Objc_class est defini dans objc/objc-class.h. Nous allons nous en servir pour tirer les informations de l’environnement d’exécution. Examinons-en les parties.
struct objc_class {
struct objc_class *isa;
struct objc_class *super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
Les deux premiers éléments sont des pointeurs vers des structs objc_class. Le premier est le pointeur isa de la classe. Les classe-objets sont des objets à part entière : ils ont un pointeur isa vers leur classe. Cela fonctionne de la manière suivante : l’isa d’un objet pointe sur sa classe. Cette classe (struct objc_class) contient toutes les variables d’instance (objc_ivar_list) déclarées dans la classe, mais son objc_method_list contient seulement les méthodes d’instance définies avec la classe.
La classe sur laquelle pointe l’isa de classe (la classe de la classe) contient les méthodes de classe de la classe dans sa objc_method_list. Compris ? La terminologie utilisée pour l’environnement d’exécution est la suivante : alors que l’isa d’un objet pointe sur sa classe, l’isa d’une classe pointe sur la “méta classe” de l’objet.
Que dire de plus à propos du pointeur isa de la méta classe ? Bon, il pointe sur la classe racine de la hiérarchie (NSObjectdans la plupart des cas). Dans la Foundation framework, chaque méta classe d’une sous-classe d’NSObject “est une” (NDT : “isa” dans le texte original) instance de NSObject. Ah oui, d’ailleurs, l’isa de la méta classe NSObject pointe sur la même struct — c’est une définition circulaire de sorte qu’aucun isa de classe n’est jamais NULL.
Et le point important de tout cela : la classe de l’objet a les méthodes d’instance, la classe de la classe (c’est-à-dire la méta classe) a les méthodes de classe.
La partie suivante de la structure est la super_class, un pointeur sur la superclasse de la classe : la classe dont elle hérite. Le pointeur super_class est NULL pour les classes racines dans la hiérarchie (i.e. NSObject).
Le const char *name est, une grosse surprise : le nom de la classe.
long version enregistre les informations sur la version du compilateur avec laquelle la classe a été compilée. L’environnement d’exécution le vérifie pour voir si certaines fonctionnalités sont disponibles pour une certaine classe. Nous pouvons ignorer ce champs.
long info contient les informations sur la structure de la classe : si c’est une méta-classe, si elle se fait passer pour une autre classe, etc. De même, son utilisation reste limitée au fonctionnement interne de l’environnement d’exécution, et nous allons l’ignorer. Le fichier objc/objc-class.h contient une liste de définitions de valeurs (NDT : #defined values, dans le texte original) (juste après la définition de struct objc_class) si cela vous interesse.
long instance_size est le nombre d’octets occupés par une instance de cette classe.
Chacune des variables d’instance d’une classe (ivars) est représentée par une struct objc_ivar qui contient le nom (ivar_name), le type d’information encodé (ivar_type) et l’offset du ivar à partir de l’adresse mémoire de l’instance. Vous pouvez accéder aux structs des ivars d’une classe en parcourant ses struct objc_ivar_list *ivars.
OK, assez de cours abstrait. Utilisons nos connaissances. Jetons un coup d’oeil à cette source d’information. Voilà comment montrer les ivars :
void showIvars(Class klass) {
int i;
Ivar rtIvar;
struct objc_ivar_list* ivarList = klass->ivars;
if (ivarList!= NULL && (ivarList->ivar_count>0)) {
printf (" Instance Variabes:\n");
for ( i = 0; i < ivarList->ivar_count; ++i ) {
rtIvar = (ivarList->ivar_list + i);
printf (" name: '%s' encodedType: '%s' offset: %d\n",
rtIvar->ivar_name, rtIvar->ivar_type, rtIvar->ivar_offset);
}
}
}
Soyez prevenus : vous allez recevoir beaucoup d’informations. Il serait mieux d’afficher ses informations détaillées pour chaque classe. Notez que ce code affiche les informations de la même manière qu’ils sont encodés dans l’environnement d’exécution. Le RuntimeBrowser décode l’information dans la forme plus familiaire d’un fichier .h
De la même manière, vous pouvez récupérer de l’information sur les méthodes implémentées par une classe. Souvenez-vous qu’une classe contient les méthodes d’instance, vous devez donc utiliser la méta classe (klass‘ isa) pour récupérer l’information sur les méthodes de classe. Voici une fonction qui affiche les infos sur les méthodes.
void showMethodGroups(Class klass, char mType) {
void *iterator = 0; // Method list (category) iterator
struct objc_method_list* mlist;
Method currMethod;
int j;
while ( mlist = class_nextMethodList( klass, &iterator ) ) {
printf (" Methods:\n");
for ( j = 0; j < mlist->method_count; ++j ) {
currMethod = (mlist->method_list + j);
printf (" method: '%c%s' encodedReturnTypeAndArguments: '%s'\n", mType,
(const char *)currMethod->method_name, currMethod->method_types);
}
}
}
De nouveau, ceci produit beaucoup d’information, et il serait donc mieux de visualiser les détails sur une seule classe à la fois.
Les éléments restants de struct objc_class contiennent :
- Le cache, qui est utilié pour accélerer le “dispatch” des messages. La première fois qu’une méthode est invoquée sur une classe, le résultat de la recherche est stocké dans le cache — les recherches suivantes sont RAPIDES.
- La liste des protocols auxquels la classe se conforme. Si vous êtes interessés par l’affichage des noms des protocoles, reportez vous à RuntimeBrowser.
Remarquez que dans showMethodGroups() il y a une boucle imbriquée. Que se passe-t-il là ? Et bien, la boucle intérieure est pour les méthodes, et la boucle extérieure est au dessus des groupes de méthodes ou … catégories. Donc là, avec un petit peu plus de connaissance sur la méthode de recherche, nous pouvons répondre à la question que vous avions posée dans le premier article : si une classe possède deux méthodes qui ont le même nom (au moins l’une d’entre-elles doit être dans une catégorie) laquelle sera invoquée ?
Pendant la recherche des méthodes, l’environnement d’exécution parcourt les tableaux de méthodes dans l’ordre dans lequel ils sont stockés. Cet ordre est l’ordre contraire de l’ordre dans lequel chaque groupe de méthode est chargé en mémoire. Puisque la classe doit être chargée avant que les catégories ne puissent lui être ajoutées, les méthodes déclarées dans la classe elle-même doivent toujours être chargées en premier, et donc finissent dernières dans la liste. Comme l’environnement d’exécution fait la recherche des méthodes en partant du début vers la fin, une méthode de catégorie remplacera toujours la méthode déclarée dans la classe elle-même.
Cependant, si deux différentes catégories definissent la même méthode, alors tout va dépendre de l’ordre dans lequel les paquets (NDT : bundles dans le texte) qui contiennent ces catégories sont chargés quand l’application est lancée. Dans la plupart des cas, vous pouvez contrôler cet ordre en forçant l’ordre dans lequel ces paquets sont chargés quand le programme démarre et en se méfiant des paquets qui se chargent dynamiquement. Mais la règle générale est : ne jamais redéfinir des méthodes de catégories dans une autres catégories puisque cela mène à des résultats non-uniformes.
S’il vous plait, faites savoir à moi-même et aux gars de ProjectOmega, ce que vous pensez de cet article. Si vous avez trouvé des erreurs, ou avez pensé à des moyens d’améliorer la présentation de ce document, veuillez prendre contact avec le traducteur de cet article, dont les coordonnées se trouvent en bas de cette page.
Liens
Un autre tutoriel sur le langage Objective-C, écrit par Pejvan, est disponible à l’adresse suivante : Apprendre l’Objective-C (pdf).
Le site de l’auteur de ce tutoriel : www.prajna.com.

Textes originaux en anglais sur O’Reilly : Inside the Objective-C Runtime, Part one par Ezra Epstein
Chargement
Commentaires récents