Éléments du langage de programmation
Le langage de programmation Objective-C étend le langage de programmation C avec les concepts et constructions suivants :
- Les objets (les instances de classes)
- Les classes
- L'héritage et le sous-typage
- Les champs
- Les méthodes et appels de méthodes (ou messages)
- Les catégories
- Les protocoles
- Les déclarations (étendues du C pour inclure les types de classes et de protocoles)
- Les types, les constantes et les variables prédéfinis
Les sections suivantes décriront ces aspects d'Objective-C.
Objets
Les objets sont les valeurs composées (données et opérations) au coeur de la programmation orientée objet. Ce sont des généralisations de types structurés simples que l'on trouve en C et dans de nombreux autres langages. Comme les structures C, les objets ont des composantes (champs de données) conservant l'état. Par exemple, un objet Livre peut avoir des champs pour spécifier l'auteur et l'éditeur. De plus, les objets interagissent via la transmission de messages, ce qui offre une alternative aux appels de fonctions d'un langage procédural.
Les objets Objective-C ont les attributs suivants :
Attributs | Description |
---|---|
Classe | Un type d'objet. Un objet dont le type est MyClass est dit être une instance de MyClass. |
Champs | Membres de données d'un objet. Un objet possède ses propres copies des champs déclarés par sa classe ou ses classes ancêtres. Les champs sont également appelés «variables d'instance». |
Méthodes | Fonctions fournies par un objet. Un objet répond aux méthodes déclarées par sa classe ou ses classes ancêtres. |
Les objets Objective-C sont implémentés de telle sorte que :
- Ils existent dans une mémoire allouée dynamiquement.
- Ils ne peuvent pas être déclarés sur la pile ou passés par valeur à d'autres portées.
- Ils ne sont référencés que par des pointeurs.
Quand quelqu'un dit d'une variable Objective-C : «c est une instance de C», vous devez comprendre que cela signifie que c est un pointeur vers un objet de type C.
Classes
Les classes sont des types en Objective-C. L'interface d'une classe spécifie la structure de ses instances ; l'implémentation fournit son code. L'interface et l'implémentation d'une classe sont séparées, généralement dans des fichiers différents. Les catégories s'ajoutent à une classe existante sans la sous-classer; elles ont également une interface et une implémentation distinctes. Les protocoles sont des déclarations d'interface pures.
Les classes déclarent les attributs suivants :
Attributs | Description |
---|---|
Classe parente | La classe dont les méthodes et les champs seront hérités par la nouvelle classe déclarée. |
Nom de la classe | Le nom de la classe déclarée. |
Champs | Membres de données de chaque instance de la classe. |
Méthodes d'instance | Fonctions associées aux instances de la classe. |
Méthodes de classe | Fonctions associées à la classe elle-même. |
Étant donné qu'Objective-C rend l'appel de méthodes de classe syntaxiquement identique à l'appel de méthodes d'instance, les classes elles-mêmes se comportent comme des objets.
Déclaration d'une interface
Une interface nomme une classe et déclare son parent (le cas échéant), ses champs et ses méthodes, sans spécifier d'implémentation. (Vous ne pouvez pas utiliser de méthodes en ligne de style C++, implémentées dans l'entête.) Un fichier d'entête peut contenir n'importe quel nombre de déclarations d'interface, mais il est conventionnel de placer chaque interface dans un fichier d'en-tête séparé. Par défaut, gcc s'attend à ce que le nom de fichier se termine par .h.
Voici un exemple de déclaration d'interface :
- #import "Graphic.h"
-
- @class Point;
-
- @interface Circle : Graphic {
- @protected // ou @public ou @private
- float radius;
- Point* center;
- }
- -(void)scaleBy:(float)factor;
- +(int)numCircles;
- @end
- La directive #import est similaire à la directive #include de C, sauf que le compilateur s'assure qu'aucun fichier n'est inclus plus d'une fois. Vous devez toujours importer la déclaration de la classe parente de votre classe (le cas échéant). Vous n'avez pas besoin d'importer d'autres déclarations de classe Objective-C, mais il peut être pratique d'importer des fichiers d'entêtes génériques de manière routinière.
- Vous utilisez la déclaration @class lorsque les champs de votre classe, ou les valeurs de retour ou les paramètres des méthodes de votre classe, sont des instances d'une autre classe. Vous pouvez utiliser des déclarations @class distinctes pour des classes distinctes, ou utiliser une déclaration unique avec les noms de classe séparés par des virgules.
- Spécifiez le nom de votre classe et celui de la classe parent (le cas échéant). Le nom de votre classe sera visible pour tout code incluant le fichier d'entête. Tous les noms de classe existent dans l'espace de noms global, ainsi que les noms de variables globales et les noms de types.
- Les mots clefs d'accès contrôlent la vérification de l'accès aux champs au moment de la compilation. Vous pouvez les répéter aussi souvent que vous le souhaitez; un champ dispose de l'autorisation d'accès spécifiée par le mot-clef précédent le plus récent. S'il n'y a pas de mot-clef précédent, l'autorisation d'accès par défaut est protégée.
- Déclarez les champs de la même manière que vous déclarez les membres d'une structure en C. Les champs peuvent être de n'importe quel type C, ainsi que de n'importe quelle classe ou autre type ajouté par Objective-C. Les champs peuvent avoir le même nom que les méthodes de la même classe.
- Vous incorporez d'autres objets uniquement par pointeur, et non par valeur. Les types prédéfinis id, Class et Protocol d'Objective-C sont déjà des types pointeurs.
- Pas de point-virgule après l'accolade fermante.
- Une méthode d'instance est marquée par un caractère «-». Les méthodes d'instance fonctionnent sur les instances de la classe. Les signatures de méthode utilisent la syntaxe infixe d'Objective-C.
- Une méthode de classe est indiquée par un caractère «+». Les méthodes de classe exécutent des opérations ou renvoient des informations sur la classe dans son ensemble et ne se rapportent à aucune instance de la classe.
- Une méthode de classe est marquée par un caractère «+». Les méthodes de classe exécutent des opérations ou renvoient des informations sur la classe dans son ensemble et ne se rapportent à aucune instance de la classe.
- Pas de point-virgule après le mot-clef @end.
Une déclaration de classe n'a pas besoin d'informations supplémentaires sur une autre classe, vous n'avez donc pas besoin d'importer un entête à moins que l'entête ne contienne d'autres déclarations (par exemple, des macros ou des variables globales) dont votre déclaration de classe a besoin.
En bref, les champs publics sont visibles par toutes les sous-classes et tout le code externe, les champs protégés ne sont visibles que par les sous-classes et les champs privés ne sont visibles que dans la classe déclarée.
Les champs ne sont pas partagés entre les instances, c'est-à-dire qu'Objective-C ne prend pas en charge les variables de classe. Mais vous pouvez obtenir le même effet en déclarant des variables ordinaires comme statiques dans le fichier d'implémentation.
Vous n'avez pas besoin de redéclarer une méthode si vous en héritez. Il est conventionnel de redéclarer une méthode si votre classe l'hérite et la remplace. Les méthodes peuvent avoir le même nom que les champs de la même classe, et les méthodes d'instance peuvent partager des noms avec les méthodes de classe.
Implémentation d'une classe
Vous implémentez une classe en écrivant le code (c'est-à-dire les corps) de chacune des méthodes de la classe. Un fichier peut contenir n'importe quel nombre d'implémentations de classe, mais il est conventionnel de placer chaque implémentation dans un fichier séparé. Par défaut, gcc s'attend à ce que le nom de fichier se termine par .m (pour Objective-C) ou .mm ou .M (pour Objective-C++). Même si une classe n'a pas de méthodes, elle doit avoir une implémentation vide.
Remarque : si vous ne fournissez pas d'implémentation de classe, le compilateur n'émettra pas de code de support pour la classe et l'éditeur de liens échouera.
Voici une implémentation simple pour la classe déclarée dans la section précédente :
- #import "Circle.h"
-
- static int count;
-
- @implementation Circle
- // Aucune section du champ.
- +(int)numCircles { return count; }
- -(void)scaleBy:(float)factor { radius *= factor;}
- @end
- Importez toujours le fichier d'entête déclarant votre classe. Si votre code utilise d'autres classes (par exemple, il leur envoie des messages ou à leurs instances), vous devez également importer les entêtes de ces classes. Il n'y a pas grand intérêt à utiliser une déclaration @class ici - si vous utilisez une autre classe dans une implémentation, vous aurez besoin de sa déclaration d'interface.
- Il s'agit d'une déclaration C pure, réservant de l'espace pour une variable par classe. (Dans cet exemple, elle serait utilisée pour compter le nombre d'objets créés. Ce code n'est pas affiché.) Il ne sera visible que dans ce fichier d'implémentation.
- Spécifiez le nom de la classe que vous implémentez.
- Vous ne pouvez pas ajouter d'autres champs ici, il n'y a donc pas de section délimitée par des accolades correspondant à celle de la déclaration d'interface.
- Définissez les méthodes avec la même syntaxe de signature que dans l'en-tête ; suivez chaque définition de méthode avec le code, entre accolades.
- Pas de point-virgule après le mot-clef @end.
L'héritage et sous-typage
L'héritage et le sous-typage sont les fonctionnalités fondamentales orientées objet : l'héritage vous permet de déclarer en quoi un type diffère d'un autre, et le sous-typage vous permet de substituer un type d'objet à un autre lorsque leurs types sont compatibles. L'interface d'une classe peut déclarer qu'elle est basée sur une autre classe :
- @interface
- MaClasse : Parent
Ceci est décrit en disant que MaClasse est une sous-classe de Parent et a les effets d'exécution suivants :
- MaClasse répond à toute méthode de classe à laquelle Parent répond.
- Les instances de MaClasse ont tous les champs que possèdent les instances de Parent.
- Les instances de MaClasse répondent à toutes les méthodes d'instance auxquelles répondent les instances de Parent.
Ces effets se résument en disant que MaClasse hérite des champs et des méthodes de Parent. Ces propriétés impliquent que l'héritage est transitif : toute sous-classe de MyClass hérite également de Parent.
La déclaration d'une classe et la spécification d'un parent ont également ces effets au moment de la compilation :
- Vous pouvez faire référence aux méthodes et champs hérités.
- Vous pouvez affecter des variables déclarées comme MaClasse à des variables déclarées comme Parent.
Ceci est décrit en disant que MaClasse est un sous-type de Parent. Le sous-typage est également transitif. Par exemple, toute sous-classe de MaClasse est un sous-type de Parent.
Vous ne pouvez pas redéclarer les champs d'une sous-classe, que ce soit de manière identique ou avec un type différent. (Contrairement à C++, cela est valable même si le champ hérité a été déclaré privé.)
Une sous-classe peut remplacer ou surcharger une méthode héritée par une version différente. Pour ce faire, vous devez fournir une nouvelle implémentation de la méthode lorsque vous écrivez la sous-classe. Au moment de l'exécution, les instances de la sous-classe exécuteront le nouveau code de la méthode de substitution au lieu du code de la méthode héritée. Par définition, la méthode de substitution porte le même nom que la méthode héritée ; elle doit également avoir les mêmes types de retour et de paramètre. (Vous ne pouvez pas surcharger les méthodes comme en C++.) Vous n'avez pas besoin de redéclarer la méthode de substitution, mais il est conventionnel de le faire.
Les champs
Les champs sont des membres de données d'objets. Chaque objet obtient sa propre copie des champs qu'il hérite ou déclare. À l'intérieur des méthodes d'une classe, vous pouvez faire directement référence aux champs de self, soit déclarés dans la classe, soit (s'ils ne sont pas privés) hérités :
- @interface Circle : Graphic {
- float radius; // Champ protégé.
- // ...
- @end
-
- @implementation Circle
- -(float)diameter {
- return 2 * radius; // Pas besoin de dire self->radius.
- }
- @end
Si un objet autre que self est typé statiquement, vous pouvez faire référence à ses champs publics avec l'opérateur de déréférencement :
- AClass* obj; // Saisie statique requise.
- obj->aField = 7; // En supposant qu'un champ soit public.
Modificateurs d'accès L'accès au champ d'un objet dépend de quatre facteurs :
- L'autorisation d'accès au champ (public, privé ou protégé)
- La classe du code d'accès
- La classe de l'objet La nature de l'objet : s'il s'agit du récepteur (self) ou d'une autre variable (globale, locale ou paramètre de méthode)
Modificateurs d'accès
L'accès au champ d'un objet dépend de quatre facteurs :
- L'autorisation d'accès au champ (public, privé ou protégé)
- La classe du code d'accès
- La classe de l'objet
- La nature de l'objet : s'il s'agit du récepteur (self) ou d'une autre variable (globale, locale ou paramètre de méthode)
Il n'existe pas de notion de membres en lecture seulement en Objective-C : si vous pouvez lire un champ, vous pouvez également y écrire.
Vous déclarez l'autorisation d'accès d'un champ dans l'interface de sa classe. L'un des trois mots-clefs d'accès (@public, @protected et @private) peut apparaître autant de fois que vous le souhaitez dans une déclaration d'interface et affecte tous les champs déclarés après lui jusqu'au mot-clef d'accès suivant. S'il n'y a pas de mot-clef précédent, un champ a un accès protégé.
Voici les mots-clefs et leurs effets sur l'accès aux champs d'un objet :
Mots clefs | Description |
---|---|
@public | Un champ public est visible par tout le code. |
@protected | Si l'objet est self, un champ protégé est visible. Si l'objet n'est pas self, l'accès dépend de sa classe :
|
@private | Si l'objet est self, un champ privé est visible. Si l'objet n'est pas self :
|
Les méthodes
Les méthodes sont les fonctions associées à un objet et sont utilisées comme interfaces pour interroger ou modifier l'état de l'objet, ou pour lui demander d'effectuer une action.
L'Objective-C utilise les termes «appeler une méthode» et «envoyer un message» de manière interchangeable. En effet, la répartition des méthodes ressemble moins à la déréférencement d'un pointeur de fonction qu'à la recherche d'un destinataire et à la livraison d'un message. La majeure partie du travail de recherche de méthode est effectuée au moment de l'exécution.
Vous envoyez des messages aux objets à l'aide de la syntaxe entre crochets d'Objective-C. Les noms de méthode sont associés aux paramètres à l'aide d'une syntaxe infixe empruntée à Smalltalk. L'Objective-C fournit des directives pour accéder directement au mécanisme de répartition au moment de l'exécution.
Déclaration d'une méthode
Vous déclarez une méthode dans une interface ou un protocole en spécifiant qui la gère (soit la classe, soit une instance de la classe), son type de retour, son nom et ses types de paramètres (le cas échéant). Le nom est divisé de sorte qu'une partie de celui-ci précède chaque paramètre. La forme d'une déclaration dépend du nombre de paramètres, comme décrit dans les sections suivantes.
Aucun paramètre
Déclaration | Description | |
---|---|---|
-(id)init; | Cette déclaration peut être décomposée comme suit : | |
Paramètre | Description | |
- | Indique une méthode d'instance ; utilisez un + pour les méthodes de classe. | |
(id) | Indique que le type de retour est id. La spécification d'un type de retour est facultative ; le type de retour par défaut est id. | |
init | Le nom de la méthode. |
Un paramètre
Déclaration | Description | |
---|---|---|
+(void)setVersion:(int)v; | Cette déclaration peut être décomposée comme suit : | |
Paramètre | Description | |
+ | Indique une méthode de classe. | |
( void ) | Indique que le type de retour est nul. | |
setVersion: | Le nom de la méthode. Ce nom de méthode inclut deux points, qui indiquent un paramètre à suivre. | |
( int ) | Spécifie que le paramètre est un int. Si vous omettez le type de paramètre, la valeur par défaut est id. | |
v | Le nom du paramètre. Vous avez besoin du nom à la fois dans la déclaration et dans l'implémentation, même s'il n'est utilisé que dans l'implémentation. |
Plus d'un paramètre
Déclaration | Description | |
---|---|---|
-(id)perform:(SEL)sel with:(id)obj; | Cette déclaration peut être décomposée comme suit : | |
Paramètre | Description | |
- | Indique une méthode d'instance. | |
( id ) | Indique que le type de retour est id. | |
perform: | La première partie du nom de la méthode. Ce nom de méthode comporte deux parties, une précédant chacun des paramètres. Le nom complet de la méthode est perform:with:. Les méthodes avec plus de paramètres suivent le même modèle que celui illustré ici. Les deuxième, troisième,... parties d'un nom de méthode n'ont pas besoin de caractères textuels, mais les deux points sont obligatoires. | |
( SEL ) | Spécifie que le premier paramètre a le type SEL. | |
sel | Le nom du premier paramètre. | |
with: | La deuxième partie du nom de la méthode. | |
( id ) | Spécifie que le deuxième paramètre a le type id. | |
obj | Le nom du deuxième paramètre. |
Un nombre variable de paramètres
Déclaration | Description | |
---|---|---|
-(id)error:(char*)format,...; | Cette déclaration peut être décomposée comme suit : | |
Paramètre | Description | |
- | Indique une méthode d'instance. | |
( id ) | Indique que le type de retour est id. | |
error: | Le nom complet de la méthode. Dans ce cas, le deuxième paramètre et les suivants ne sont pas précédés d'extensions du nom de la méthode. | |
( char* ) | Spécifie le type du premier paramètre. | |
format | Le nom du premier paramètre. | |
... | Représente la liste de paramètres de taille variable. Les ellipses comme celles-ci ne peuvent apparaître qu'à la fin d'une déclaration de méthode. Dans la méthode, accédez à la liste à l'aide de l'interface variable-arguments standard de C. |
Implémentation d'une méthode
Le corps d'une méthode apparaît dans la section d'implémentation d'une classe. La méthode commence par une déclaration, identique à celle de la section d'interface, et est suivie d'un code entouré d'accolades. Par exemple :
- -(void)scaleBy:(float)factor {
- radius *= factor;
- }
Dans le corps d'une méthode, faites référence aux champs que la classe déclare ou hérite sans qualification. Faites référence aux champs d'autres objets à l'aide de l'opérateur de déréférencement (->).
Objective-C définit trois variables spéciales à l'intérieur de chaque méthode :
Variables | Description |
---|---|
self | Le récepteur de l'appel de méthode. |
super | Une référence à la version héritée de la méthode, s'il y en a une. |
_cmd | Le sélecteur - un entier qui spécifie de manière unique la méthode. |
Appel d'une méthode
Chaque appel de méthode a un récepteur (l'objet dont vous appelez la méthode) et un nom de méthode. Vous placez un appel de méthode entre crochets [ ] en indiquant d'abord le récepteur, puis le nom de la méthode. Si la méthode prend des paramètres, ils suivent les deux points correspondants dans les composantes du nom de la méthode. Séparez les composantes récepteur et nom par des espaces. Par exemple :
- [aCircle initWithCenter:aPoint andRadius:42];
Si une méthode prend un nombre variable de paramètres, séparez-les par des virgules :
- [self error:"Valeur attendu à %d mais a obtenu %d", i, j];
Un appel de méthode est une expression, donc si la méthode renvoie une valeur, vous pouvez l'affecter à une variable :
- int theArea = [aCircle area];
Les appels de méthode peuvent être imbriqués :
- Circle* c1 = ... // peu importe
- Circle* c2 = [[Circle alloc] initWithCenter:[c1 center] andRadius:[c1 radius]];
Collisions de noms
Lorsque le compilateur encode un appel de méthode, il doit configurer, sur place, un espace pour les paramètres et la valeur de retour (le cas échéant). Il s'agit d'un aspect non dynamique des appels de méthode. Pour encoder correctement l'appel, le compilateur doit connaître les types de paramètres et de retour.
Si vous laissez le type d'un récepteur inconnu (c'est-à-dire déclaré comme id) jusqu'à l'exécution, le nom d'une méthode peut être la seule information dont dispose le compilateur lorsqu'il compile l'appel de méthode. Le compilateur encodera l'appel de méthode en fonction des utilisations précédentes de la même méthode :
- Si le compilateur n'a pas encore vu de méthodes portant le nom utilisé, il supposera que le type de retour et les paramètres sont tous id (un pointeur dont la taille dépend de la plateforme) et émettra un message d'avertissement. Si vous utilisez des variables de type autre que id, le compilateur les convertira selon les règles standard du C et elles risquent de ne pas survivre intactes.
- Si le compilateur a vu exactement une méthode portant le même nom, il émettra du code supposant que les types de retour et de paramètre correspondent à ceux qu'il a vus.
- Si le compilateur a vu plusieurs méthodes portant le même nom et des types de retour ou de paramètre différents, il ne saura pas comment encoder l'appel de méthode. Il choisira une option et émettra un message d'avertissement. Comme dans le premier cas, les conversions de type peuvent perturber vos valeurs de données.
Le compilateur ne vous permettra pas de déclarer, dans des classes liées, des méthodes portant le même nom mais des types de paramètres différents (pas de surcharge de style C++), mais vous pouvez le faire dans des classes non liées. Pour éviter de forcer le compilateur à deviner vos intentions, les méthodes portant le même nom, même dans des classes différentes, doivent généralement avoir la même signature.
Vous pouvez assouplir cette restriction si vous savez que le récepteur de l'appel sera toujours typé statiquement. Mais vous n'aurez peut-être pas le contrôle sur toute utilisation future de votre méthode.
Méthodes privées
Étant donné que le compilateur doit connaître les types de paramètres pour générer des appels de méthode, vous ne devez pas non plus omettre les déclarations de méthode pour tenter de les rendre privées. Une meilleure façon de rendre une méthode privée est de déplacer la déclaration de méthode vers le fichier d'implémentation de sa classe, où elle sera visible pour le code l'utilisant mais pas pour toute autre partie de votre programme.
L'exemple suivant montre comment utiliser une catégorie pour déclarer une méthode dans un fichier d'implémentation :
- @interface
- Circle (PrivateMethods)
- -(float)top;
- @end
-
- @implementation
- Circle
- // Définitions publiques comme avant.
- ...
- // Implémentez maintenant les méthodes privées.
- -(float)top { return [center y] - radius; }
- @end
- Au début du fichier d'implémentation de votre classe, déclarez une catégorie étendant la classe.
- Les méthodes déclarées ici font partie de la classe au même titre que celles déclarées dans l'interface normale, mais ne seront visibles que dans ce fichier.
- L'implémentation de votre classe, de la manière habituelle.
- Implémentez les méthodes déclarées dans le fichier d'en-tête. Elles peuvent appeler les méthodes privées (ainsi que d'autres méthodes publiques, bien sûr).
- Implémentez les méthodes privées. Elles peuvent appeler des méthodes publiques et privées.
Accesseurs
Les accesseurs sont des méthodes permettant de définir et de renvoyer les champs d'un objet. L'Objective-C a plusieurs conventions pour écrire des accesseurs :
- Pour renvoyer une variable, fournissez une méthode portant le même nom que la variable. Vous pouvez le faire car les méthodes et les champs ont des espaces de noms distincts.
- Pour définir une variable, fournissez une méthode portant le même nom que la variable prenant la nouvelle valeur comme paramètre. Par exemple, étant donné une variable nommée radius, utilisez radius:( float )r comme signature pour votre méthode setter. Vous pouvez le faire car les deux points précédant le paramètre font partie du nom des méthodes, donc radius: the setter sera nommé différemment de radius the getter.
Voici un exemple de la façon d'écrire des accesseurs pour un champ simple :
- @implementation Circle
- -(float)radius { return radius; }
- -(void)radius:(float)r { radius = r; }
- @end
Si vous utilisez le comptage de références, il existe d'autres modèles de conception que vous devez suivre lors de l'écriture d'accesseurs.
Chemins de recherche de messages
Lorsque vous envoyez un message à un objet (appelez l'une de ses méthodes), le code qui s'exécute réellement est déterminé par une recherche effectuée au moment de l'exécution.
La répartition de l'appel de méthode se déroule comme suit :
- Le système d'exécution examine l'objet au moment réel de l'envoi du message et détermine la classe de l'objet.
- Si cette classe possède une méthode d'instance portant le même nom, la méthode s'exécute.
- Si la classe ne possède pas une telle méthode, la même recherche a lieu dans la classe parent.
- La recherche remonte l'arbre d'héritage jusqu'à la classe racine. Si une méthode portant un nom correspondant est trouvée, cette méthode s'exécute.
- S'il n'existe aucune méthode correspondante mais que le récepteur possède une méthode de transfert, le système d'exécution appelle la méthode de transfert. Le comportement de transfert par défaut consiste à quitter le programme.
- S'il n'existe aucune méthode de transfert, le programme se termine normalement avec une erreur.
Récepteurs spéciaux
Le récepteur est l'objet auquel vous envoyez un message. Ses méthodes d'instance auront la première chance de gérer l'appel, suivies des méthodes d'instance de ses classes ancêtres.
Outre le cas typique d'envoi d'un message à un récepteur nommé, il existe plusieurs cibles spéciales :
Cible | Description |
---|---|
self | Fait référence à l'objet qui exécute la méthode en cours. Au moment de l'exécution, la classe de self peut être la classe dans laquelle l'appel à self apparaît, ou il peut s'agir d'une sous-classe. |
super | Fait référence à la version héritée de la méthode. Lorsque vous utilisez la variable prédéfinie super comme récepteur, vous envoyez un message à self mais vous demandez au runtime de démarrer la recherche de méthode dans la classe parent de la classe dans laquelle l'envoi se produit. Ce n'est pas la même chose que de démarrer dans la classe parent du récepteur : la classe de super est déterminée au moment de la compilation. Si cela était fait de manière dynamique, le code suivant provoquerait une boucle infinie :
Ici, D est une sous-classe de C qui ne remplace pas f. L'appel à la ligne 8 serait envoyé à la ligne 2, mais l'appel à super serait redirigé vers la classe parent (C) du récepteur (D) - c'est-à-dire vers la ligne 2. |
N'importe quel nom de classe | Fait référence à l'objet de classe qui représente la classe nommée. Lorsque vous appelez une méthode de classe, vous envoyez en fait un message à un objet de classe. Si vous n'avez pas l'objet de classe sous la main, cette syntaxe vous permet d'utiliser le nom de la classe comme récepteur. |
nil | Ce n'est pas une erreur d'envoyer un message à nil (un objet non initialisé ou effacé). Si le message n'a pas de valeur de retour (void), rien ne se passera.
Si le message renvoie un pointeur d'objet, il renverra nil. Si le message renvoie une valeur scalaire telle qu'un int ou un float, il renverra zéro.
Si le message renvoie une structure, une union ou une autre valeur composée, sa valeur de retour n'est pas spécifiée et vous ne devez pas en dépendre. Ce comportement
facilite l'enchaînement des appels de méthode. Par exemple :
Si l'envoi d'un message à nil provoquait une erreur d'exécution, vous devriez vérifier le résultat de chaque message. Cette propriété de nil ne devrait pas vous inciter à être laxiste avec votre vérification d'exécution, mais elle peut suffire à tester uniquement le résultat final d'une chaîne d'appels de méthodes. |
Les sélecteurs
Les méthodes Objective-C se distinguent par leurs noms, étant la concaténation des composants du nom de la méthode, y compris les deux-points. Au moment de la compilation, chaque nom est associé à un entier unique appelé sélecteur. Le type SEL d'Objective-C représente le type d'un sélecteur.
Lorsqu'un appel de méthode est compilé, il est distillé en son récepteur, son sélecteur et ses paramètres. Au moment de l'exécution, le message est envoyé en faisant correspondre le sélecteur avec une liste maintenue par l'objet de classe du récepteur.
Vous pouvez utiliser des sélecteurs pour prendre une décision d'exécution sur la méthode à appeler : la version Objective-C des pointeurs de fonction ou de méthode.
- SEL mySel = @selector(center);
- Point* p = [aCircle perform:mySel];
- Le compilateur connaît la correspondance entre les noms de sélecteur et les SEL et émettra du code attribuant à mySel la valeur correspondant au nom de la méthode center.
- À l'aide du sélecteur, vous pouvez demander au récepteur de répondre au message comme s'il avait été envoyé de la manière habituelle. L'effet est le même que l'exécution de l'appel de méthode direct :
- Point* p = [aCircle center];
Les catégories
L'Objective-C fournit la construction de catégorie pour modifier une classe existante «sur place». Cela diffère de l'écriture d'une nouvelle sous-classe : avec l'héritage, vous ne pouvez créer qu'une nouvelle feuille dans l'arbre d'héritage ; les catégories vous permettent de modifier les nouds intérieurs de l'arbre. Avec une catégorie, la déclaration complète d'une classe peut être répartie sur plusieurs fichiers et compilée à des moments différents. Cela présente plusieurs avantages :
- Vous pouvez partitionner une classe en groupes de méthodes liées et garder les groupes séparés.
- Différents programmeurs peuvent travailler plus facilement sur différentes parties de la classe.
- Pour diverses applications, vous pouvez fournir uniquement les parties de la classe qui sont nécessaires. Cela vous donne un contrôle plus précis sur l'expression des dépendances sans avoir à fournir différentes versions de la classe.
- Vous pouvez ajouter des méthodes aux classes à partir de la bibliothèque du système d'exploitation ou de sources tierces sans avoir à les sous-classer.
Déclaration d'une catégorie
Voici un exemple de déclaration d'une catégorie pour ajouter des méthodes à la classe Circle :
- #import "Circle.h"
-
- @interface Circle(Motion)
- // Aucune section de terrain.
- -(void)moveRight:(float)dx;
- -(void)moveUp:(float)dy;
- @end
- La déclaration peut se trouver dans le même fichier d'en-tête que la déclaration de la classe qu'elle modifie, ou dans un fichier séparé. Si elle se trouve dans un fichier d'entête séparé, vous devrez peut-être inclure le fichier d'entête de la classe modifiée.
- Déclarez le nom de la classe que vous modifiez et le nom de votre catégorie. Dans cet exemple, Circle est le nom de la classe et Motion est le nom de la catégorie. Les noms de catégorie ont leur propre espace de noms, donc une catégorie peut avoir le même nom qu'une classe ou un protocole.
- Il n'y a pas de section de champs dans la déclaration de catégorie, vous ne pouvez donc pas utiliser une catégorie pour ajouter des champs à une classe.
- Déclarez ici les méthodes comme dans une interface de classe.
- Pas de point-virgule après le mot-clef @end.
Vous pouvez déclarer une catégorie dans un fichier d'entête ou d'implémentation. Si la déclaration se trouve dans un fichier d'entête, ses méthodes sont des membres à part entière de la classe modifiée. Tout code peut utiliser les nouvelles méthodes avec la classe modifiée (et ses sous-classes). Si la déclaration se trouve dans un fichier d'implémentation, seul le code de ce fichier peut utiliser les nouvelles méthodes, et leur implémentation doit apparaître dans ce fichier. C'est une façon de rendre les méthodes privées.
Si vous déclarez dans votre catégorie une méthode portant le même nom qu'une méthode de la classe modifiée, la nouvelle méthode remplace l'ancienne. Lorsqu'une telle méthode de substitution envoie des messages à super, ils vont à la même méthode dans la classe parent (comme ils le feraient dans la version remplacée) et non à la méthode remplacée elle-même. Vous ne pouvez pas envoyer de messages à la version remplacée. Vous devez simplement éviter d'utiliser des catégories pour remplacer des méthodes.
Si plusieurs catégories qui modifient la même classe déclarent toutes une méthode portant le même nom, les résultats dépendent de l'implémentation. En d'autres termes, ne faites pas cela. Le compilateur gcc ne vous avertit pas à ce sujet.
Vous n'êtes pas obligé d'implémenter toutes les méthodes que vous déclarez dans une catégorie, ni même une partie d'entre elles. Le compilateur vous avertira si vous avez une section d'implémentation pour votre catégorie et omettra des méthodes. Si vous n'avez aucune implémentation pour une catégorie, cela s'appelle déclarer un protocole informel.
Implémentation d'une catégorie
Vous implémentez les méthodes de la catégorie dans un fichier d'implémentation, comme ceci :
- #import "Motion.h"
-
- @implementation Circle (Motion)
- -(void)moveRight:(float)dx { ... }
- -(void)moveUp:(float)dy { ... }
- @end
- Vous devez inclure la déclaration de la catégorie que vous implémentez. Si la déclaration se trouve dans le même fichier que l'implémentation, vous n'avez pas besoin de cette ligne.
- Spécifiez le nom de la classe modifiée et le nom de votre catégorie.
- Les méthodes prennent la même forme et ont le même accès aux champs que dans une implémentation de classe standard.
- Pas de point-virgule après le mot-clef @end.
Les protocoles
Les protocoles sont des listes de déclarations de méthodes n'étant pas associées à une déclaration de classe spécifique. Les protocoles vous permettent d'exprimer que des classes non liées partagent un ensemble commun de déclarations de méthodes. (Cette fonctionnalité a inspiré la construction d'interface de Java.) Les protocoles vous permettent d'effectuer les opérations suivantes :
- Utiliser la vérification de type statique là où vous le souhaitez
- Spécifier des interfaces vers d'autres codes
- Factoriser les caractéristiques communes de votre hiérarchie de classes
Les déclarations Objective-C peuvent spécifier qu'une instance doit prendre en charge un protocole, au lieu (ou en plus) de se conformer à une classe.
Déclaration d'un protocole
Vous déclarez un protocole dans un fichier d'entête comme ceci :
- @protocol AnotherProtocol;
-
- @protocol Printable <Drawable>
- -(void)print;
- @end
- Vous avez besoin de la déclaration de protocole forward si les types de retour ou de paramètre des méthodes de votre protocole utilisent l'autre protocole. Les noms de protocole ont leur propre espace de noms, donc un protocole peut avoir le même nom qu'une classe ou une catégorie.
- Si votre protocole étend d'autres protocoles, nommez-les dans la liste des protocoles. La liste est une séquence de noms de protocoles, séparés par des virgules. Si la liste est vide, vous pouvez omettre les crochets angulaires. Vous n'avez pas besoin de redéclarer les méthodes des protocoles répertoriés. Lorsqu'une classe adopte le protocole que vous déclarez, elle doit implémenter toutes les méthodes déclarées dans votre protocole et tous les autres protocoles de la liste. (Vous ne pouvez pas écrire des classes partiellement abstraites comme en C++, avec certaines méthodes déclarées mais non implémentées.)
- Vous déclarez ici vos méthodes sous la même forme que dans une interface de classe.
- Pas de point-virgule après le mot-clef @end.
Adopter un protocole
Lorsque vous souhaitez que votre classe adopte (implémente) un ou plusieurs protocoles, vous le déclarez comme ceci :
- #import "Printable.h"
- @interface Circle : Graphic <Printable>
Lorsque vous souhaitez que votre catégorie adopte un protocole, vous le déclarez comme ceci :
- #import "Printable.h"
- @interface Circle (Motion) <Printable>
La liste des protocoles, à l'intérieur des chevrons, est composée de noms séparés par des virgules. Vous devez importer les fichiers d'entête où les protocoles sont déclarés. Ne redéclarez pas les méthodes du protocole dans votre interface ; définissez-les simplement dans votre implémentation. Vous devez définir toutes les méthodes du protocole.
Lorsque vous déclarez un champ ou une variable, vous pouvez spécifier qu'il représente une instance dont la classe est conforme à un protocole spécifique comme ceci :
- id <Printable> obj;
- ClassName <Printable>* obj;
Vérification de la conformité à un protocole
Au moment de l'exécution, vous pouvez vérifier si la classe d'un objet est conforme à un protocole en utilisant les méthodes -conformsTo : (depuis la classe racine Object) ou +conformsToProtocol : (depuis NSObject) :
- [obj conformsTo:@protocol(Printable)];
Comme les classes, les protocoles ont des structures d'exécution spéciales leur étant associées, sont appelées objets de protocole.
Protocoles informels
Vous pouvez obtenir certaines des fonctionnalités d'un protocole en déclarant mais sans implémenter une catégorie. Une telle catégorie est appelée protocole informel. Vous ne pouvez pas déclarer qu'une classe implémente ou non un protocole informel, vous ne pouvez donc pas l'utiliser pour la vérification de type statique. Vous pouvez utiliser un protocole informel pour spécifier un groupe de méthodes que toutes les sous-classes de la classe modifiée peuvent implémenter, mais ne sont pas obligées d'implémenter. Cela sert à quelque chose de moins qu'un protocole complet, mais plus qu'une simple documentation textuelle. Si vous avez besoin d'un protocole, il est préférable d'utiliser un protocole formel.
Lorsqu'une sous-classe implémente un protocole informel, elle ne fait pas référence à la déclaration d'origine, mais déclare dans son interface les méthodes qu'elle va implémenter et définit les méthodes de son implémentation de la manière habituelle.
Déclarations
Vous pouvez déclarer des objets Objective-C de différentes manières. Cependant, la manière dont vous déclarez un objet n'a aucun effet sur le comportement d'exécution de cet objet. Au contraire, la manière dont vous déclarez un objet contrôle la manière dont le compilateur vérifie la sécurité de type de votre programme.
Typage dynamique
Utilisez le type id pour déclarer un pointeur vers un type d'objet non spécifié. C'est ce qu'on appelle le typage dynamique. Par exemple :
- id obj;
Avec cette déclaration, obj peut être un pointeur vers un objet de n'importe quel type. Un id possède les propriétés de compilation suivantes :
- Vous pouvez envoyer n'importe quel type de message à un id. Si, au moment de l'exécution, l'objet n'a pas de méthode ou de délégué approprié, une erreur d'exécution se produira.
- Vous pouvez affecter n'importe quel autre objet à un id.
- Vous pouvez affecter un id à n'importe quel objet. Le compilateur ne vous rappellera pas que cela peut être dangereux. Utiliser un id de cette manière signifie que vous assumez le risque d'affecter un objet à une variable incompatible.
Typage statique
En Objective-C, tout écart par rapport au typage entièrement dynamique est appelé typage statique. Avec le typage statique, vous indiquez au compilateur les types de valeurs que vous souhaitez attribuer aux variables. Tout typage statique est effectué dans la relation d'héritage : lorsque la classe ou le protocole d'une variable est déclaré, une valeur d'une classe ou d'un protocole descendant est toujours acceptable.
Vous pouvez utiliser le typage statique de trois manières, illustrées dans les exemples suivants :
- MyClass * obj
Déclare obj comme étant une instance de MyClass ou l'un de ses descendants. Ceci est appelé typage statique. Déclarer obj de cette manière a les effets suivants au moment de la compilation :
- Vous pouvez affecter obj à n'importe quelle variable de type id.
- Vous pouvez affecter n'importe quelle variable de type id à obj.
- Vous pouvez affecter obj à n'importe quelle variable dont le type déclaré est MyClass ou l'un de ses ancêtres.
- Vous pouvez affecter à obj n'importe quelle variable dont le type déclaré est MyClass ou l'un de ses descendants.
- Le compilateur vous avertira si vous affectez obj ou si vous lui affectez d'une manière non couverte dans les cas précédents. Un cast calmera le compilateur mais n'empêchera pas une affectation incompatible au moment de l'exécution.
- Vous pouvez envoyer à obj n'importe quel message que MyClass ou l'une de ses classes parentes déclare.
- id <ProtocolList> obj
Ne contraint pas la classe de obj, mais déclare qu'elle est conforme aux protocoles spécifiés. La liste des protocoles se compose de noms de protocoles séparés par des virgules. Cette déclaration a les effets suivants au moment de la compilation :
- Vous pouvez affecter obj à un objet qui n'est pas conforme au protocole.
- L'affectation à obj d'un objet qui n'est pas déclaré conforme déclenchera un avertissement du compilateur.
- L'envoi à obj d'un message non inclus dans le protocole déclenchera un avertissement du compilateur.
- MyClass <ProtocolList>* obj
Déclare que obj est une instance de MyClass ou de l'un de ses descendants, et qu'elle est conforme aux protocoles listés. Les effets de cette déclaration au moment de la compilation sont la combinaison des effets des deux styles de déclaration précédents.
Qualificateurs de type
Les qualificateurs de type se placent avant un type C ou Objective-C dans une déclaration de méthode et modifient ce type. Les qualificateurs pris en charge sont :
- bycopy
- byref
- in
- inout
- oneway
- out
Ces qualificateurs ne peuvent être utilisés que dans des protocoles et des implémentations formels, et non dans des déclarations de classe ou de catégorie. Ils spécifient comment les paramètres doivent être transmis lorsque vous utilisez la messagerie à distance. Les qualificateurs de type peuvent être combinés, bien que toutes les combinaisons ne soient pas judicieuses.
Types, constantes et variables prédéfinis
L'Objective-C ajoute le type spécial id, étant générique comme un void * en C++, mais qui ne vous empêche pas d'envoyer des messages. De plus, l'environnement Objective-C fournit certains types, constantes et variables C pour prendre en charge la programmation orientée objet.
Les types
En plus des types de classe que vous définissez, Objective-C introduit ces types intégrés que vous pouvez utiliser lors de la déclaration de variables :
Type | Description |
---|---|
id | Un type de pointeur d'objet Objective-C générique. Vous pouvez envoyer n'importe quel message aux variables de ce type sans avertissement du compilateur. Lors de l'exécution, le récepteur peut gérer le message, le déléguer ou l'ignorer explicitement. S'il ne fait rien de tout cela, une exception d'exécution se produit. Les types de retour ou de paramètre de méthode non spécifiés sont par défaut id. Le nom id est un typedef C pour un pointeur vers une structure avec un membre : un champ qui pointe vers un objet de classe. |
Class | Un type C pur : un pointeur vers une structure de classe Objective-C. Le système d'exécution contient une structure pour chaque classe Objective-C dans le code de votre programme. L'appel de la méthode -class de n'importe quel objet renvoie une valeur de ce type. |
MetaClass | Fourni dans l'environnement d'exécution GNU mais pas dans Darwin, il est identique à Class. Il est utilisé pour plus de clarté lors de l'utilisation d'objets de métaclasse. |
Protocol | Une classe Objective-C. Le système d'exécution contient une instance pour chaque protocole Objective-C étant soit adopté par une classe dans le code de votre programme, soit référencé dans une directive de déclaration @protocol. Vous pouvez construire une instance de protocole à partir de son nom en utilisant la directive @protocol avec un paramètre. |
BOOL | Un type logique dont les variables ont deux valeurs possibles : YES et NO. Le nom BOOL est un typedef pour le type C char. |
SEL | Un spécificateur unique pour un sélecteur de méthode. Souvent implémenté comme un pointeur vers le nom de la méthode, mais vous devez le traiter comme un descripteur opaque. Vous pouvez construire un sélecteur à partir d'un nom de méthode en utilisant la directive @selector. |
IMP | Un pointeur vers l'implémentation d'une méthode qui renvoie un identifiant. Plus précisément, un IMP est défini de cette façon :
Les deux premiers paramètres sont les valeurs du récepteur et du sélecteur étant transmises à n'importe quelle méthode; la liste de paramètres de longueur variable restante contient les paramètres visibles de la méthode. Vous pouvez construire un IMP à partir d'un sélecteur de messages en utilisant les méthodes des classes racines Object ou NSObject. L'appel d'une méthode via son IMP est considérablement plus rapide que l'utilisation d'une répartition classique, mais peut ne pas fonctionner correctement si la méthode ne renvoie pas d'identificateur. |
Les constantes
Les constantes suivantes sont toutes définies comme des symboles de préprocesseur :
Constantes | Description |
---|---|
nil | Une valeur décrivant un identificateur non initialisé ou non valide. Défini comme étant zéro. |
Nil | Une valeur décrivant une variable de classe non initialisée ou non valide. Définie comme étant zéro. |
YES | Une valeur de type BOOL, décrivant un état vrai. Définie comme étant de type un. |
NO | Une valeur de type BOOL décrivant un état faux. Définie comme étant zéro. |
Les variables
Ce sont des variables spéciales, configurées pour vous par l'environnement d'exécution Objective-C :
Variables | Description |
---|---|
self | À l'intérieur d'une méthode d'instance, cette variable fait référence au récepteur du message ayant invoqué la méthode. |
super | Ce n'est pas vraiment une variable, mais elle y ressemble suffisamment pour être incluse dans cette liste. À l'intérieur d'une méthode, si vous envoyez un message à super, la méthode de redistribution commencera à chercher dans la classe parent de la classe de la méthode. Autrement dit, super représente le parent statique, et non le parent au moment de l'exécution. |
_cmd | Une variable de type SEL, disponible dans n'importe quelle méthode Objective-C, et décrivant le sélecteur de la méthode. |
isa | Il s'agit d'un champ protégé de tous les objets Objective-C. Vous y avez accès, même s'il est préférable de l'obtenir à partir de la méthode de classe :
isa pointe vers l'objet de classe représentant la classe de l'objet. |