Section courante

A propos

Section administrative du site

É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 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 :

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 :

  1. #import "Graphic.h"
  2.  
  3. @class Point;
  4.  
  5. @interface Circle : Graphic {
  6.  @protected // ou @public ou @private
  7.   float radius;
  8.   Point* center;
  9.  }
  10.  -(void)scaleBy:(float)factor;
  11.  +(int)numCircles;
  12. @end 

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 :

  1. #import "Circle.h"
  2.  
  3. static int count;
  4.  
  5. @implementation Circle
  6.   // Aucune section du champ.
  7.   +(int)numCircles { return count; }
  8.   -(void)scaleBy:(float)factor { radius *= factor;}
  9. @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 :

  1. @interface
  2.   MaClasse : Parent

Ceci est décrit en disant que MaClasse est une sous-classe de Parent et a les effets d'exécution suivants :

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 :

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 :

  1. @interface Circle : Graphic {
  2.   float radius; // Champ protégé.
  3.   // ...
  4. @end
  5.  
  6. @implementation Circle
  7.   -(float)diameter {
  8.     return 2 * radius; // Pas besoin de dire self->radius.
  9. }
  10. @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 :

  1. AClass* obj;     // Saisie statique requise.
  2. 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 :

Modificateurs d'accès

L'accès au champ d'un objet dépend de quatre facteurs :

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 :
  • Si la classe de l'objet est la même que celle du récepteur, le champ est visible.
  • Si l'objet hérite de la classe du récepteur, le champ n'est pas visible. (Vous pouvez contourner ce problème en convertissant l'objet en classe du récepteur.)
  • Si l'objet a une autre relation avec la classe du récepteur, le champ n'est pas visible.
@private Si l'objet est self, un champ privé est visible. Si l'objet n'est pas self :
  • Si la classe de l'objet est la même que celle du récepteur, le champ est visible.
  • Dans tous les autres cas, le champ n'est pas visible.

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 :

  1. -(void)scaleBy:(float)factor {
  2.   radius *= factor;
  3. }

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 :

  1. [aCircle initWithCenter:aPoint andRadius:42];

Si une méthode prend un nombre variable de paramètres, séparez-les par des virgules :

  1. [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 :

  1. int theArea = [aCircle  area];

Les appels de méthode peuvent être imbriqués :

  1. Circle* c1 = ... // peu importe
  2. 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 :

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 :

  1. @interface
  2.      Circle (PrivateMethods)
  3. -(float)top;
  4. @end
  5.  
  6. @implementation 
  7.     Circle
  8. // Définitions publiques comme avant.
  9.    ...
  10. // Implémentez maintenant les méthodes privées.
  11. -(float)top { return [center y] - radius; }
  12. @end

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 :

Voici un exemple de la façon d'écrire des accesseurs pour un champ simple :

  1. @implementation Circle
  2.   -(float)radius { return radius; }
  3.   -(void)radius:(float)r { radius = r; }
  4. @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 :

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 :

  1. @implementation C
  2.    -(void) f { [super f]; }
  3. @end
  4. // Et ailleurs :
  5. D* d = [D new];
  6. // D hérite de C, ne remplace pas f.
  7. [d  f];  // S'exécute dans la version C.


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 :

  1. id  widget = [[[window child] toolbar] widget];

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.

  1. SEL mySel = @selector(center);
  2. Point* p = [aCircle perform:mySel];

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 :

Déclaration d'une catégorie

Voici un exemple de déclaration d'une catégorie pour ajouter des méthodes à la classe Circle :

  1. #import "Circle.h"
  2.  
  3. @interface Circle(Motion)
  4.  // Aucune section de terrain.
  5.  -(void)moveRight:(float)dx;
  6.  -(void)moveUp:(float)dy;
  7. @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 :

  1. #import "Motion.h"
  2.  
  3. @implementation Circle (Motion)
  4.  -(void)moveRight:(float)dx { ... }
  5.  -(void)moveUp:(float)dy { ... }
  6. @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 :

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 :

  1. @protocol AnotherProtocol;
  2.  
  3. @protocol Printable <Drawable>
  4.  -(void)print;
  5. @end

Adopter un protocole

Lorsque vous souhaitez que votre classe adopte (implémente) un ou plusieurs protocoles, vous le déclarez comme ceci :

  1. #import "Printable.h"
  2. @interface Circle : Graphic <Printable>

Lorsque vous souhaitez que votre catégorie adopte un protocole, vous le déclarez comme ceci :

  1. #import "Printable.h"
  2. @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 :

  1. id <Printable> obj;
  2. 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) :

  1. [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 :

  1. 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 :

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 :

  1. 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 :

  1. 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 :

  1. 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 :

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 :

  1. typedef id (*IMP)(id, SEL, ...)


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 :

  1. ClassC = obj->isa;      // Mauvais.
  2. Class C = [obj class];  // bon.

isa pointe vers l'objet de classe représentant la classe de l'objet.


Dernière mise à jour : Jeudi, le 10 octobre 2024