Index

Savoir

Faire

Miscellaneous

ObjectiveC

 

L'Objective C est un langage dérivé du C et inspiré par SmallTalk, qui essaie de marier la performance du premier et la souplesse de développement du second. Nous allons survoler ensemble ses caractéristiques principales, afin d'appréhender sa philosophie. Une connaissance du C, ainsi que de la programmation objet, est nécessaire pour comprendre pleinement l'article.

Vue globale
Objective C ajoute des possibilités au langage C. Ces possibilités sont essentiellement une architecture objet, et une méthode de communication avec ces objets par passage de messages. On peut donc conserver le code procédural existant, et utiliser en plus les mécanismes d'encapsulation, de dérivation, etc. là où ils sont nécessaires. On évite de plus les inconvénients des langages purement objets, où tout est objet, et qui induisent souvent des pertes de performance rédhibitoires pour un langage se voulant universel.

Un premier exemple
En Objective C, les #include sont remplacés par des #import, qui assurent la même fonction, mais n'exécutent l'inclusion que si elle n'a pas déjà été faite, éliminant du même coup les problèmes liés aux inclusions multiples. Tous les objets doivent dériver d'un objet racine Object, c'est pourquoi les premières lignes du programme ne doivent pas manquer d'inclure sa définition :

#import <ObjC/Object.h>
#import <stdio.h>

Objective C différencie la définition d'un objet (qui prend en général place dans un fichier d'extension .h) de son implémentation (qui prend place dans un fichier d'extension .m). La définition d'un objet est introduite par le mot clé @interface. Suivent le nom de l'objet à définir, le nom de l'objet mère, les données membres, ainsi que les méthodes de l'objet (une méthode est en gros une fonction associée à un objet). L'objet que nous allons créer, MonObjet, se définit comme suit :

@interface MonObjet : Object
{
   int mValeur;
}
-(void)action;
-(void)prefixe;
@end

Cet objet hérite directement de l'objet de base Object, comporte un membre sous la forme d'un entier, et définit deux méthodes d'instance : action, qui ne prend aucun paramètre et renvoie void, et prefixe, qui fait de même. Chaque méthode est physiquement une fonction C, mais son appel ne passe pas uniquement par un appel de fonction. C'est en fait un message qui est envoyé à MonObjet, lui intimant l'ordre d'appeler la méthode en question. Nous verrons plus tard comment.

L'implémentation est aussi triviale que la définition. Le mot clé l'introduisant est @implementation.

@implementation MonObjet
-(void)action
{
   [self prefixe];
   printf ("L'objet vaut %d\n", mValeur);
}
-(void)prefixe
{
   puts ("Prefixe");
}
@end

La méthode action commence par faire un appel à la méthode prefixe, appartenant au même objet. Nous apercevons en première ligne d'action le mécanisme d'appel de méthode, qui utilise des crochets [] : le destinataire du message, ici l'objet lui-même représenté par self, est suivi du nom de la méthode à exécuter. La simplicité de cette syntaxe n'a d'égale que sa puissance. En effet, le passage de message peut aussi s'effectuer d'un espace d'adressage à un autre, c'est-à-dire d'un programme à un autre, ou d'une machine à une autre, sans grande complication supplémentaire !

Dériver un objet, pour redéfinir une de ses méthodes par exemple, est une nouvelle fois d'une simplicité désarmante :

@interface MonObjetDerive : MonObjet
-(void)prefixe;
@end
 
@implementation MonObjetDerive
-(void)prefixe
{
   puts ("Prefixe dérivé");
}
@end

MonObjetDerive hérite directement de l'objet que nous venons de créer un peu plus tôt. Il modifie le comportement de la méthode prefixe. Cette redéfinition passe par une déclaration dans l'@interface, et une nouvelle implémentation, dans @implementation bien entendu.

Le corps de notre programme principal commence par le traditionnel :

int main (int argc, const char *argv[])
{

Nous allons ensuite créer un objet MonObjetDerive. Il faut tout d'abord allouer la place nécessaire, puis initialiser l'objet (par défaut, l'espace occupé par l'objet est mis à zéro). Ces actions sont réalisées par les méthodes alloc et init, qui appartiennent à l'objet de base Object et qui ont été héritées tout au long du processus de dérivation. Ces méthodes sont des méthodes de classe, et pas d'instance. En effet, il est impossible que ces méthodes appartiennent à un objet, puisque l'objet n'est pas encore créé !
Il faut donc enchaîner deux appels successifs à ces méthodes, ce qui permettra d'obtenir au final un pointeur sur un objet
MonObjetDerive.

   MonObjet *objet = [[MonObjetDerive alloc] init];

Il est tout à fait légal d'assigner un pointeur sur un objet MonObjetDerive à un pointeur sur un objet ObjetDerive, vu qu'un objet MonObjetDerive EST un objet MonObjet. Cependant, le typage n'est pas si rigoureux en Objective C qu'en C, en C++ ou en Java. Il n'est qu'une indication pour le programmeur, la résolution de l'appel d'une méthode se faisant à l'exécution : Objective C vérifiera donc à l'exécution que l'objet est à même de recevoir et d'exécuter le message qui lui est destiné, quel que soit le typage utilisé lors de la programmation. Avec pour conséquence remarquable qu'envoyer un message à un objet NULL est une non-opération : rien à voir avec C ou C++, pour lesquels les actions sur NULL sont souvent synonymes de plantages.
Le chaînage de messages se fait par imbrication des crochets. Encore une fois, la syntaxe est simple, et n'est pas ambigüe : on comprend aisément que la méthode
alloc sera appelée avant la méthode init, et que cette dernière portera sur l'objet renvoyé par alloc. Rien à voir une nouvelle fois avec la complexité artificielle créée par les priorités entre les opérateurs ., ->, () et j'en passe du C++.

   [objet action];

L'appel à la méthode action de notre cru, nécessite quelques commentaires. L'action de MonObjetDerive est l'action de MonObjet. Celle-ci appelle prefixe. Or prefixe a été redéfinie dans MonObjetDerive. Vu que l'objet que nous avons créé est un MonObjetDerive (bien qu'il soit utilisé au travers d'un pointeur sur un MonObjet), c'est bien prefixe de MonObjetDerive qui est appelé.
Le
printf suivant affiche la valeur de mValeur, qui vaut 0 par défaut puisque l'objet a été intrégralement mis à zéro par l'intermédiaire de init. L'appel de méthode ci-dessus fournit donc la sortie suivante :

Prefixe dérivé
L'objet vaut 0

Afin de terminer le programme dans les règles de l'art, on libère la place prise par l'objet précédemment créé, et on fait retourner à main un code d'erreur rassurant :

   [objet free];
   exit(0);
}

Conclusion
Nous avons aperçu le mode de fonctionnement d'Objective C. Celui-ci ne remet nullement en question les acquis du programmeur C. Il permet par contre, de manière très simple, de construire une hiérarchie objet puissante, de par sa cohérence, et de par le principe du passage de messages qui couvre à lui tout seul quasiment tous les besoins de déclenchement d'action d'un programme.

Nous verrons dans un prochain article quelques autres aspects remarquables de ce langage.