Cette série d'articles a pour but de présenter Objective-C, langage de programmation à objet conçu par Brad J. Cox vers le milieu des années 80. L'idée de Brad était de doter le C des principales capacités de SmallTalk 80.
On pourrait se contenter de résumer rapidement : Objective-C, un autre « super C » offrant un système de classes et de messages comparable à celui de SmallTalk, mais par l'introduction de nouveaux mots clés autorisant de nouvelles constructions, c'est un outil alliant puissance et une incomparable souplesse d'utilisation que Brad J. Cox a mis à notre disposition.
Au cours de cette présentation, nous reviendrons sur les concepts clés de la programmation à objets : l'héritage, le polymorphisme, le typage dynamique et/ou statique et tout particulièrement sur le déplacement de la prise de décision du processus de la compilation à celui du runtime. En effet, ce déplacement rend l'exécution du code compilé des programmes à objets directement dépendante de leurs runtime-system. Nous détaillerons ultérieurement le runtime d'Objective-C ; aujourd'hui, nous nous occuperons du langage et le runtime ne sera évoqué que par certaines de ses caractéristiques qui permettront une meilleure compréhension des spécificités d'Objective-C.
Introduction
Il existe plusieurs compilateurs ObjC (voir : @ en fin d'article). Le compilateur GNU supporte Objective-C depuis longtemps et offre de plus la possibilité d'utiliser plusieurs librairies de classes dont celles du projet GNUstep (http://www.gnu step.org/), qui implémentent l'API d'OpenStep. OpenStep a été développé d'abord par NeXT, puis conjointement avec Sun, qui abandonnera rapidement le projet pour recentrer ses efforts sur Java. Le projet GNUstep reprend l'ensemble des spécifications du projet OpenStep et offre sous licence GPL les premières versions stables d'un véritable environnement de programmation à objets entièrement écrit en Objective-C. GNUstep ne manquera pas d'être l'objet d'une présentation dans les colonnes de Linux Magazine et cet article vous aidera, je l'espère, à mieux en profiter.
Quelques définitions
Objets
Un objet associe des données et des opérations particulières qui sont capables d'utiliser et de modifier ces données. En Objective-C, ces opérations sont appelées méthodes d'objets, les données qu'elles affectent sont quant à elles appelées variables d'instance.
id
En Objective-C, les objets sont identifiés par un type distinct de données, id. Ce type est défini comme un pointeur sur un objet, en réalité sur les données d'un objet (ses variables d'instance). Comme une fonction C ou un array, un objet est identifié par son adresse. Tous les objets, indépendamment de leurs variables d'instance et de leurs méthodes sont de type id.
id myObject;
Pour les constructions orientées objet d'Objective-C, telles des méthodes, id remplace int en tant que type par défaut (pour des constructions strictement C, telles que des fonctions retournant une valeur, le type par défaut demeure int ). Le mot clé nil est défini comme un objet nul, un id avec une valeur égale 0.
Typage Dynamique
Le type id, outre qu'il ne connaît qu'une information sur l'objet et qu'il est un objet, est absolument non-restrictif. Au cours de son exécution, un programme aura besoin d'avoir d'autres d'informations à propos de cet objet : quelles sont ses variables d'instance, ses méthodes, etc. Le type id ne fournissant pas ces informations, chaque objet doit être capable de les fournir au runtime.
Richtext : un éditeur WYSIWYG écrit on objective
C, capable de gérer les fichiers rtf, html et texte.
(Cliquez pour agrandir)
C'est grâce à la variable d'instance isa, que chaque objet transporte avec lui et qui identifie sa classe, que ces informations seront accessibles. Chaque objet peut informer le runtime qu'il est un rectangle, un cercle ou quoique ce soit d'autre.
Les objets sont de plus dynamiquement typés au runtime. Quels que soient ses besoins, il suffit alors au runtime d'interroger l'objet lui-même pour obtenir toute l'information requise.
Le pointeur isa contribue aussi aux capacités introspectives des objets. Les compilateurs conservent la plus grande partie des informations qui leur sont fournies par le code source et les réorganisent sous forme de structures utilisées par le runtime. C'est le pointeur isa qui permet aux objets de retrouver cette information et de la révéler au runtime.
Messages
Pour qu'un objet entre en action, nous devons lui envoyer un message lui indiquant d'exécuter une méthode. En Objective-C les messages sont entourés de crochet :
[receveur message]
Le receveur est un objet, le message lui indique l'opération effectuer. Le codage consiste à taper le nom d'une méthode et de tout argument qui lui est passé. Dès qu'un message est envoyé, le runtime sélectionne la méthode appropriée dans le répertoire de l'objet receveur puis l'invoque.
Le message :
[myRect display];
demande à l'objet myRect d'invoquer la méthode display qui assure l'affichage du rectangle.
Le système de messages accepte aussi les méthodes prenant des arguments.
[myRect setOrigin:30.0 :50.0];
est un message adressé à l'objet myRect pour lui signifier les coordonnées de son origine (30.0, 50.0).
Ici la méthode setOrigin:: est suivie de deux fois le caractère ":" , une fois pour chacun des arguments. Les arguments sont insérés après les ":" de manière à isoler le nom de la méthode. Les ":" ne doivent pas nécessairement être groupés directement à la suite du nom de la méthode, comme au début de ce paragraphe. Généralement, un mot clé décrivant les arguments précède chaque ":" . La méthode setWidth:height: prend par exemple deux arguments :
[myRect setWidth:10.0 height:15.0];
Sont également supportées les méthodes qui prennent un nombre variable d'arguments. Ces arguments occasionnels sont alors séparés par une virgule après chaque nom de méthode. Dans l'exemple suivant, la méthode makeGroup: reçoit l'argument (group) et trois autres arguments qui sont optionnels.
[receiver makeGroup:group, memberOne, memberTwo, memberThree];
Comme les fonctions C, les méthodes peuvent retourner des valeurs. L'exemple suivant ajuste la valeur de la variable isFilled Vrai si myRect est rempli d'une couleur ou Faux si seul son périmètre a été dessiné.
BOOL isFilled;
isFilled = [myRect isFilled];
Un message peut contenir un message. Ici, la couleur d'un rectangle est ajustée à la couleur d'un autre :
[myRect setPrimaryColor:[otherRect primaryColor]];
Un message adressé nil est également correct,
[nil setOrigin:100.0 :22.5];
mais n'a tout simplement pas d'effet et, en fait, assez peu de sens. Les messages adressés nil retournent simplement nil.
Les Variables d'Instance du Receveur
Une méthode a un accès automatique aux variables d'instance de l'objet receveur. Il n'est pas nécessaire de passer ces variables d'instance en tant qu'arguments. Dans l'exemple ci-dessus, la méthode primaryColor ne prend pas d'argument bien qu'elle soit capable de retrouver la primaryColor pour otherRect et de la retourner. Chaque méthode prend en charge le receveur et ses variables d'instance sans qu'il soit nécessaire de les déclarer en tant qu'arguments.
Une méthode a un accès automatique aux seules variables d'instance du receveur. Si cette méthode doit connaître certaines informations propres à une variable stockée dans un autre objet, elle doit alors interroger à l'aide d'un message cet autre objet lui demandant de révéler le contenu de sa variable. Les méthodes primaryColor et isFilled utilisées dans l'exemple ci-dessus illustrent ce propos.
Définition de Classe
L'interface :
Un objet rectangle très basique :
// Fichier: Rectangle .h
#import <Foundation/NSObject.> // besoin d'une
// superclass
@interface Rectangle : NSObject // déclaration de // class
{ // déclaration des variables d'instance
int length; // variable d'instance
int width; // variable d'instance
} // variables d'instance fin
// déclarations des méthodes
- ( void ) setLength : ( int ) newWidth; // Accesseurs pour width
- ( void ) setLength : ( int ) newlength; // Accesseurs pour 'length'
- ( int ) width;
- ( int ) length;
@end //déclaration de class fin
La ligne @interface ClassName : Superclass: déclare Rectangle comme nom d'une classe descendante de NSObject.
{
les variables d'instance sont déclarées à l'intérieur des accolades,
}
les méthodes ici, avant le
@end
qui balise la fin de la déclaration de l'objet Rectangle.
La super classe définit la position de la nouvelle classe dans la hiérarchie d'héritage (nous reviendrons sur la notion d'héritage en Objective-C). Ne pas taper les ":" et le nom de la super classe équivaut à déclarer, dans notre exemple, une classe racine concurrente de NSObject.
Pour déclarer le type de la valeur retour d'une méthode, on utilise la syntaxe C du cast :
- (int)width;
La déclaration du type des arguments utilise la même syntaxe :
- setWidth : ( int ) newWidth;
Une valeur retour ou un argument dont le type n'est pas explicitement défini prend le type par défaut des méthodes et des messages : l'id. Lorsque nous avons une méthode prenant plusieurs arguments, ils sont déclarés à la suite du nom, séparés par ":":
- ( void ) setWidth : ( int ) width : height : ( int ) height;
L'implémentation :
La définition d'une classe est structurée comme sa déclaration ; on retrouve les directives @implementation au début et @end pour finir.
Les méthodes, comme les fonctions C, sont définies par leur nom suivi du bloc d'accolades contenant les instructions.
// Fichier: Rectangle .m
#import "Rectangle .h" // notre interface
@implementation Rectangle // début de définition de //la classe
- ( void ) setLength : ( int ) newLength
{
length = newLength;
}
- ( int ) length
{
return length;
}
- ( void ) setWidth : ( int ) newWidth
{
width = newWidth;
}
- ( int ) width
{
return width;
}
@end // Fin de définition de la classe
Création d'instances
Le code suivant demande à la classe Rectangle de créer une nouvelle instance de Rectangle et de l'assigner à la variable myRect.
id myRect;
myRect = [Rectangle alloc];
La méthode alloc alloue dynamiquement la mémoire nécessaire aux variables d'instance et les initialise toutes à 0, à l'exception de la variable isa, qui connecte l'instance nouvellement créée à sa classe. Dans un cas concret, ces variables auront sans doute besoin d'être plus complètement initialisées. La méthode init réalise cette tâche immédiatement après l'allocation.
myRect = [[Rectangle alloc] init];
Cette ligne de code est absolument nécessaire pour que myRect puisse recevoir quelque message que ce soit. L'allocation renvoie une nouvelle instance et cette instance demande à la méthode init d'ajuster son état initial. Chaque classe d'objet possède au moins une méthode d'allocation (alloc) qui lui permet de produire de nouvelles instances qui, à leur tour, possèdent au moins une méthode d'initialisation (init). Ces méthodes d'initialisation prennent généralement un ou plusieurs arguments qui autorisent le passage de valeurs et possèdent des mots clés permettant de labelliser les arguments. Notre classe Rectangle pourrait avoir par exemple une méthode initWithSize:Position: pour la création d'une nouvelle instance de l'objet Rectangle avec une taille et une position donnée.
En Objective-C, les variables d'instance d'un objet sont internes à l'objet lui-même ; nous n'avons accès à l'état d'un objet qu'à l'aide des méthodes de l'objet. Si d'autres objets doivent obtenir des informations le concernant, tout objet doit offrir une ou plusieurs méthodes capables de fournir ces informations.
De la même manière que les fonctions C protègent leurs variables locales, les rendant invisibles au reste d'un programme, un objet dissimule à la fois ses variables d'instance et l'implémentation de ses méthodes.
// Programme ObjectiveTest
// fichier: "ObjectiveTest.m"
#import "Rectangle.h" // nécessaire pour la variable //'myRect'
main ()
{
anObject *myRect; // Variable locale
myRect = [ [ Rectangle alloc ] init ]; // Allo cation de la mémoire et initialisation
[ myRect setWidth : 7 ];
[ myRect setLength : 14 ];
printf ( "My Rect length is %d and is %d'width.\n",
[ myRect length ], [ myRect width ] );
}
Voilà, nous avons fait un rapide survol de la grammaire d'Objective-C. Bien des choses restent cependant à étudier : la liaison dynamique (Dynamic binding), la notion d'héritage en Objective-C, le fonctionnement des messages, les protocoles, etc. Dans de prochains articles, nous nous appuierons sur GNUstep, dont l'implémentation d'OpenStep nous permettra de développer des programmes propres à affiner notre connaissance d'Objective-C.
La présence d'Objective-C sur le Net
http://www.slip.net/~dekorte/Objective-C/
http://developer.apple.com/techpubs/macosx/macosx.html
http://perso.wanadoo.fr/yann.leguen/
Cette liste n'est évidement pas exhaustive ! Mais la première adresse, bien que n'ayant pas été mise à jours depuis quelques mois, contient tous les liens nécessaires pour surfer au gré de votre curiosité ou de vos besoins.
Les Compilateurs d'Objective-C
GNU
GNU CC supporte Objective-C depuis la version 2, egcs (version expérimentale de gcc) également. C'est le compilateur de référence pour GNUstep.
Apple/NeXT
Voir http://developer.apple.com/techpubs/macosx/macosx.html. Il s'agit en fait d'une version « customiser » par NeXT, puis par Apple, de GNU CC.
Stepstone
Historiquement le premier, offre un compilateur, une runtime
librairie et nombre de matériels :
DEC Stations, HP, IBM RISC, Apple, Sun, Silicon Graphics, etc.
Stepstone offre également les librairies de classes suivantes :
- ICpak101 Foundation Class Library
- ICpak201 GUI Class Library, cette dernière n'étant disponible que pour les plates-formes qui supportent XWindows, Motif, OpenWindows, SunView.
Comparaisons
Les principales différences entre tous ces compilateurs :
NeXT | GNU | StepStone | |
Protocols & Categories | Y | Y | N |
Static Binding | N | N | Y |
Static Typing | Y | Y | Y |
Native Debugger | Y | Y | Y |
GUI builder | Y | Coming soon ! | N |
La classe Object (classe racine) diffère sensiblement pour chacune de ces implémentations.
Stepstone offre par exemple l'ensemble « Foundation class libraries » qui fonctionne sur l'ensemble des plates-formes supportées, y compris NeXT.
Le compilateur Apple/NeXT supporte les libraries OpenStep.
Le compilateur GNU supporte les libraries GNUstep.
POC (Portable Object Compiler)
La particularité de ce compilateur est qu'il est écrit entièrement en Objective-C. Il est disponible pour de nombreuses plates-formes, Windows, MacOS, Sun etc. Il est disponible à : ftp: //sunsite.cnlab-switch.ch/software/ « votre plate-forme favorite ».