Cours Les bases d’Objective-C en pdf


Télécharger Cours Les bases d’Objective-C en pdf

★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Télécharger aussi :


La comparaison avec le C# nécessiterait un autre document, car ce langage est beaucoup plus proche de l'Objective-C que le C++. Un programmeur C# s'adapte sans doute assez facilement à l'Objective-C. D'après moi, le C#, malgré des concepts est tout de même très inférieur à l'Objective-C, à cause de sa complexité pour des choses simples d'accès en Objective-C, et une API .NET très loin de la qualité de Cocoa. Cet avis est personnel et son argumentation dépasse le cadre du présent document. 

Une première précision me semble indispensable : Objective-C est un langage, et Cocoa est un ensemble de classes de programmer de façon native sous MacOS X. En théorie, on peut faire de l'Objective-C sans Cocoa : il existe un front-end pour GCC. Sous MacOS X, les deux sont presque indissociables, la plupart des classes fournies avec le langage faisant en réalité partie de Cocoa.

être précis, Cocoa est l'implémentation par Apple, pour MacOS X, du standard OpenStep publié par NeXT Computer en 1994. Il s'agit d'une bibliothèque de développement d'applications basée sur Objective-C. Citons l'existence de GNUstep [6], une autre implémentation, libre, d'OpenStep. Cette implémentation se veut la plus portable possible pour fonctionner avec de nombreux systèmes. Elle est toujours en développement au moment où j'écris ces lignes.

Il est assez difficile de dater précisément les naissances des langages de programmation. Selon que l'on considère leurs balbutiements, leur développement, leur annonce officielle ou leur standardisation, la marge est importante. Malgré tout, un historique sommaire, présenté en figure 1, permet de situer Objective-C vis-à-vis de ses ancêtres et ses concurrents.

Figure 1 - Historique sommaire de Smalltalk, Java, C, C#, C++ et Objective-C

Smalltalk-80 est un des tous premiers langages réellement objet. Le C++ et l'Objective-C sont deux branches différentes visant à créer un langage objet basé sur le C. Objective-C emprunte énormément à Smalltalk, pour le dynamisme et la syntaxe, tandis que C++ se tourne vers une forme de programmation très statique orientée vers la performance d'exécution. Java veut se placer en remplaçant du C++ mais il est également très inspiré de Smalltalk par son aspect objet plus pur. C'est pourquoi, malgré le titre de ce document, de nombreuses références sont également faites au Java. Le langage C#, développé par Microsoft, est un concurrent direct d'Objective-C.

Objective-C++ est une fusion de l'Objective-C et du C++. Il est déjà en grande partie opérationnel, mais certains comportements sont encore imparfaits à la date d'écriture de ces lignes. Objective-C++doit permettre de mélanger des syntaxes Objective-C et C++ pour tirer parti des fonctionnalités des deux mondes (cf. section 14 page 67).

Le présent document a été mis à jour pour tenir compte des modifications apportées par Objective-C 2.0, introduit avec MacOS 10.5. Bien qu'importants techniquement, les nouveaux outils qu'apportent ces modifications aux développeurs s'énumèrent assez rapidement. Il s'agit :

Chacune de ces nouveautés est détaillée dans une section spécifique.

Comme en C ++, un programme correctement écrit en C devrait être compilable en Objective-C, sauf s'il utilise certaines mauvaises pratiques autorisées par le C. L'Objective-C n'a fait que rajouter des concepts et les mots-clefs les accompagnant. Pour éviter tout conflit, ces mots-clefs commencent par le caractère @ (at). En voici la (courte) liste exhaustive : @class, @interface, @implementation, @public, @private, @protected, @try, @catch, @throw, @finally, @end, @protocol, @selector, @synchronized, @encode, @defs (qui n'est plus documenté dans [4]). Objective-C 2.0 (cf. 1.3 page précédente) a ajouté @optional, @required, @property, @dynamic, @synthesize. Notons également la présence des valeurs nil et Nil, du type id, du type SEL et du type BOOL avec ses valeurs YES et NO. Enfin, il existe quelques mots-clefs qui ne sont définis que dans un certain contexte bien particulier, et qui ne sont pas réservés hors de ce contexte : in, out, inout, bycopy, byref, oneway (ceux-là peuvent se rencontrer dans la définition de protocoles : cf. section 4.4.5 page 25) et getter, setter, readwrite, readonly, assign, retain, copy, nonatomic (ceux-là peuvent se rencontrer dans la définition des propriétés : cf. section 12.2 page 61).

Il y a une très forte confusion possible entre les mots-clefs du langage et certaines méthodes héritées de la classe racine NSObject (la mère de toutes les classes, cf. section 3.1 page 10). Par exemple, les apparents « mots-clefs » de la gestion mémoire que sont alloc, retain, release et autorelease, sont en fait des méthodes de NSObject. Les mots super et self (cf. section 3.3.1 page 13), pourraient également passer pour des mots-clefs, mais self est en fait un argument caché de toute méthode, et super une directive demandant au compilateur d'utiliser self différemment. Il ne me paraît cependant pas préjudiciable de faire l'amalgame entre ces faux « mots-clefs » et les vrais, vu leur indispensable présence.

Les commentaires /* ... */ et // sont autorisés.

Il est possible comme en C++ d'insérer des  variables au milieu d'un bloc d'instructions.

En C++, le type bool a été  En Objective-C, on dispose du type BOOL qui prend les valeurs YES ou NO.

Ces trois mots-clefs sont expliqués dans la suite de ce document. On peut toutefois les présenter brièvement :

-   Tout objet est de type id. C'est un outil de typage faible;

-   nil est l'équivalent pour un pointeur d'objet. nil et NULL ne sont pas interchangeables;

-   Nil est l'équivalent de nil pour un pointeur de classe, car en Objective-C, les classes sont aussi des objets (instances de méta-classes).

SEL est le type utilisé pour stocker les sélecteurs, obtenus par l'utilisation de @selector. Un sélecteur représente une méthode sans objet associé : on peut l'utiliser comme équivalent à un pointeur de méthode. Ce n'est cependant pas, techniquement, un pointeur de fonction. Voyez la section 3.3.5 page 16 pour des explications à ce sujet.

2.4.4    @encode

Dans un souci d'interopérabilité, les types de données en Objective-C, même les types personnalisés et les prototypes de fonctions et méthodes, peuvent être encodés en caractères ASCII, selon un format documenté [4]. Un appel à @encode(un type) envoie une chaîne au format C standard (char*) représentant le type.

Comme en C++, il est bienvenu d'utiliser un couple de fichiers interface/implémentation par classe. En Objective-C, le fichier d'en-tête est un fichier .h et le code est dans un fichier .m ; on rencontre aussi l'extension .mm pour l'Objective-C++, qui fait l'objet d'une section spéciale en fin de document (section 14 page 67). Enfin, notons que l'Objective-C introduit la directive de compilation #import pour remplacer avantageusement #include. En effet, tout fichier d'en-tête en C doit posséder des gardes de compilation pour empêcher son inclusion multiple; le #import gère cela automatiquement. Ci-après se trouve un exemple typique de couple interface/implémentation. La syntaxe Objective-C est expliquée dans la suite du document.

Dans ce document, la plupart des classes sont précédées de NS, comme NSObjectNSString... La raison en est simple : ce sont des classes Cocoa, et la plupart des classes Cocoa commencent par NS puisqu'elles ont été initiées sous NeXTStep.

C'est une pratique courante que d'utiliser un tel préfixe afin d'identifier l'origine des classes.

Objective-C n'est pas un langage « dont les appels de fonction s'écrivent avec des crochets ». C'est en effet ce que l'on pourrait penser en voyant écrit

[object doSomething]; au lieu de

object.doSomething();

D'une .. comme Objective-C est un sur-ensemble du C, les fonctions respectent la même syntaxe et la même logique que le C quant à la déclaration, l'implémentation et l'appel. En revanche, lesméthodes, qui n'existent pas en C, ont une syntaxe avec des crochets. De plus, la différence ne se situe pas seulement au niveau de la syntaxe mais également de la sémantique. Cela est expliqué plus en détails dans la section 3.2 page suivante : ce n'est pas un appel de méthode, c'est l'envoi d'un message. Même si l'effet est semblable pour la structure du code, l'implémentation de ce mécanisme permet plus de souplesse et de dynamisme. Il est par exemple compatible avec l'ajout de méthodes pendant l'exécution (cf. section 13.2 page 66). La syntaxe est aussi beaucoup plus claire, surtout en cas d'appels en cascade (cf. section 3.3.1 page 13). 



Objective-C est un langage objet : on y crée des classes et des objets. Il respecte cependant mieux le paradigme objet que C++, lequel présente des lacunes vis à vis du modèle idéal. Par exemple, en Objective-C, les classes sont elles-mêmes des objets manipulables dynamiquement : on peut, pendant l'exécution du programme, ajouter des classes, créer des instances d'une classe d'un certain nom, demander à une classe quelles méthodes elle implémente, etc. Tout cela est beaucoup plus puissant que les RTTI du C++ (cf. section 13.1 page 65), qui ont été greffées à un langage fondamentalement « statique ». On déconseille d'ailleurs souvent de les utiliser, car les résultats obtenus dépendent du compilateur lui-même, et ne garantissent aucune portabilité.

Dans un langage objet, on crée généralement un diagramme de classes pour chaque programme. Une des particularités d'Objective-C par rapport à C++ est l'existence d'une classe racine (root) dont devrait hériter toute nouvelle classe. En Cocoa, il s'agit de NSObject, qui fournit un nombre de services gigantesque, et assure la cohérence du système d'exécution d'Objective-C. Mais la notion de classe racine n'est pas une spécifité d'Objective-C, c'est une particularité du modèle objet en général; Smalltalk, Java, utilisent aussi une classe racine. 

En toute rigueur, tout objet devrait donc être de type NSObject et tout pointeur vers un objet pourrait ainsi être déclaré comme un NSObject*. Cependant, ce n'est pas absolument obligatoire : il existe le type id, court et pratique pour exprimer un pointeur vers un objet quelconque, et qui implique une vérification dynamique des types, et non plus statique. Cela peut être utile comme outil de typage faible sur des méthodes génériques. Notez aussi qu'un pointeur d'objet nul ne devrait pas être mis à NULL mais à nil. Ces valeurs n'ont pas vocation d'être interchangeables. Un pointeur C normal peut être à NULL, mais nil a été introduit en Objective-C pour les pointeurs d'objets. En Objective-C, les classes sont aussi des objets (instances de méta-classes), et il existe donc des pointeurs de classes, pour lesquels la valeur vide est Nil.

Il est difficile de montrer par un seul exemple toutes les différences existant entre le C++ et l'Objective-C pour la déclaration des classes et l'implémentation des méthodes. En effet, syntaxe et concepts s'entremêlent et nécessitent des explications. Les différences sont donc exposées en plusieurs étapes très ciblées.

En Objective-C, les attributs sont appelés données d'instance, et les fonctions membres des

méthodes.

C++

Objective-C

class Toto

{ double x;

public:

          int            f(int x);

float g(int x, int y);

};

int           Toto::f(int x) {...} float Toto::g(int x, int y) {...}

@interface Toto : NSObject

{ double x;

}

-(int)                f:(int)x;

-(float) g:(int)x :(int)y; @end

@implementation Toto

-(int)                     f:(int)x {...}

-(float) g:(int)x :(int)y {...} @end

En C++, attributs et méthodes sont déclarés ensembles au sein de la classe. Pour implémenter les méthodes, dont la syntaxe est similaire au C, on utilise l'opérateur de résolution de portée Toto:: .

En Objective-C, les attributs et les méthodes ne peuvent être mélangés. Les attributs sont déclarés entre accolades, et les méthodes leur succèdent. Le corps des méthodes est spécifié dans une partie @implementation.

Il existe une différence fondamentale avec le C++, dans la mesure où des méthodes peuvent être implémentées sans avoir été déclarées dans l'interface. Ce point est détaillé plus tard. Sommairement, cela permet d'alléger les fichiers .h en ne répétant pas dans une sous-classe les déclarations des méthodes virtuelles redéfinies qui ne sont appelées qu'automatiquement, comme un destructeur par exemple. Voyez la section 4.3.2 page 22 pour plus d'explications.

Les méthodes sont précédées du signe « - » ou parfois du signe « + », pour différencier méthodes d'instance et méthodes de classe (cf. section 3.3.9 page 19). Ce signe n'a rien à voir avec la notation UML signifiant public ou private. Les types des paramètres sont encadrés de parenthèses, et surtout les paramètres sont séparés par des « : ». Voyez la section 3.3.1 page 13 pour plus de détails sur la syntaxe des prototypes.

Notez aussi qu'il n'y a pas de point-virgule nécessaire à la fin d'une déclaration de classe en Objective-C. Remarquez également que c'est bien @interface qui est employé pour déclarer une classe, et non @class comme on aurait pu le croire. Le mot-clef @class est utilisé dans les déclarations anticipées (forward) (cf. section 3.2.2 de la présente page). Sachez enfin que si la classe ne déclare pas de données d'instance, le bloc d'accolades ouvrantes-fermantes (qui serait vide) n'est pas nécessaire.

Pour éviter les dépendances cycliques des fichiers d'en-tête, il est nécessaire de recourir à la déclaration forward des classes lorsqu'on a juste besoin de spécifier leur existence, et non leur définition. En C++, le class remplit aussi ce rôle; en Objective-C, on utilise @class. Il existe aussi le mot-clef @protocol pour anticiper la déclaration des protocoles (cf. section 4.4 page 23).

C++

//Dans le fichier Toto.h

#ifndef __TOTO_H__ #define __TOTO_H__

class Titi; //déclaration forward class Toto

{

Titi* titi;

public:

void utiliserTiti(void);

};

#endif

le fichier Toto.cpp</p>

#include "Toto.h" #include "Titi.h"

void Toto::utiliserTiti(void)

{ ...

}

Objective-C

//Dans le fichier Toto.h

@class Titi; //déclaration forward

@interface Toto : NSObject

{

Titi* titi;

}

-(void) utiliserTiti;

@end

//Dans le fichier Toto.m

#import "Toto.h"

#import "Titi.h"

@implementation Toto

-(void) utiliserTiti

{ ...

}

@end

public, private, protected

Le modèle objet repose en partie sur l'encapsulation des données, qui permet de limiter leur visibilité dans différentes portions du code, pour offrir des garanties d'intégrité.

C++

Objective-C

class Toto

{ public: int x; int tata();

protected: int y; int titi();

private: int z; int tutu();

};

@interface Toto : NSObject

{ @public: int x;

@protected: int y;

@private: int z;

}

-(int) tata;

-(int) titi;

-(int) tutu;

@end

En C++, attributs et méthodes peuvent appartenir à un contexte de visibilité public, protected ou private. Si elle n'est pas spécifiée, la visibilité est private.

En Objective-C, seules les données d'instance peuvent être public, protected ou private, et la visibilité par défaut est ici protected. Les méthodes ne peuvent être que publiques. On peut cependant imiter le mode privé, en implémentant des méthodes uniquement dans la partie

@implementation, les déclarer dans la partie @interface, ou en utilisant la notion de catégories de classe (cf. section 4.5 page 26). Cela n'empêche pas les méthodes d'être appelées, mais elles sont moins visibles. Notez que le fait de pouvoir implémenter des méthodes sans les avoir déclarées dans l'interface est une fonctionnalité propre à l'Objective-C, et qui sert un autre but, expliqué en section 4.3.2 page 22.

On ne peut pas en Objective-C spécifier un caractère public, protected ou private pour l'héritage. ll est forcément public. L'héritage en Objective-C est beaucoup plus semblable à celui du Java que du C++ (section 4 page 22).

Il n'est pas possible en Objective-C de déclarer des données de classe, (attributs static en C++). On peut en revanche les obtenir par un moyen détourné. Il suffit pour cela d'utiliser une variable globale dans le fichier d'implémentation (éventuellement munie de l'attribut static du C limitant sa portée). La classe peut alors définir des accesseurs dessus (par des méthodes de classe ou des méthodes normales), et son initialisation peut être effectuée dans la méthode initialize de la classe (cf. section 5.1.10 page 35).

Les méthodes en Objective-C est bien différente des fonctions C classiques. Cette section explique l'intérêt de cette syntaxe particulière, et déborde un peu sur le principe de l'envoi de messages, sur lequel repose Objective-C.

-   Une méthode est précédée d'un « − » si c'est une méthode d'instance (le cas le plus courant), et d'un « + » si c'est une méthode de classe (static en C++). Ce symbole n'a rien à voir avec la signification public ou private de l'UML. Rappelons que les méthodes sont toujours publiques en Objective-C;



-   les types de retour et des paramètres sont entre parenthèses;

-   les paramètres sont séparés par le caractère « : »;

-   les paramètres recevoir une étiquette, un nom avant le « : ». L'étiquette fait alors partie du nom de la méthode et le modifie. Cela rend les appels à cette fonction extrêmement lisibles. À l'usage, cette pratique est incontournable. Il est à noter que le premier paramètre ne peut recevoir d'étiquette autre que le nom de la méthode qui le précède;

-   le nom peut être strictement le même que celui d'un attribut, sans provoquerde conflit. Cela est très utile pour écrire des accesseurs en lecture (cf. section 6.4.8 page 48).

C++

//prototype

void Array::insertObject(void *anObject, unsigned int atIndex)

//utilisation avec une instance "etagere" de la classe Array

//et un objet "livre" etagere.insertObject(livre, 2);

Objective-C

Sans étiquette (transcription directe d'un prototype C++)

//prototype

//La méthode se nomme "insertObject::", les deux-points servant à séparer

//les paramètres (il ne pas d'un opérateur de résolution de portée

//comme en C++)

-(void) insertObject:(id)anObject:(unsigned int)index

//utilisation avec une instance "etagere" de la classe Array //et un objet "livre".

[etagere insertObject:livre:2];

Avec étiquette

//prototype. On assigne ici au paramètre "index" l'étiquette "atIndex".

//La méthode se nomme maintenant "insertObject:atIndex:"

//Cela permettra de lire cet appel comme une phrase

insertObject:(id)anObject atIndex:(unsigned int)index

//utilisation avec une instance "etagere" de la classe Array

//et un objet

[etagere insertObject:livre:2];          //Erreur ! [etagere insertObject:livre atIndex:2]; //OK

Notez que la syntaxe à base de crochets ne se lit pas comme appeler la méthode insertObject de l'objet « etagere » mais plutôt comme envoyer le message insertObject à l'objet « etagere ». Cette phrase traduit tout le dynamisme d'Objective-C. On peut envoyer les messages que l'on souhaite à une cible donnée. Si elle n'est pas capable de traiter ce message, elle l'ignorera (en pratique, une exception est levée, mais le programme ne s'interrompt pas). Si à un instant donné de l'exécution du programme la cible sait traiter le message, elle le fera avec la méthode correspondante. Il y a tout de même des avertissements de la part du compilateur si un message est envoyé à un objet de classe connue, pour laquelle on sait que le message est invalide; mais ce n'est pas considéré comme une erreur (du fait du possible forwarding cf. section 3.4.3 page 20). Si la cible du message n'est connue que sous le type id, il n'y a que lors de l'exécution du programme qu'il pourra être déterminé si le message peut être traité. Aucun avertissement n'est donc levé lors de la compilation.

this, self et super

Ies deux cibles particulières pour un message : self et super. self représente l'objet courant (équivalent de this en C ++), super représente la classe mère, comme en Java. Le mot-clef this n'existe pas en Objective-C, self le remplace.

Remarque : self n'est cependant pas un véritable mot-clef, c'est un paramètre caché que reçoit toute méthode, et qui représente l'objet courant. On peut en changer la valeur, contrairement au this du C++, cette pratique n'est utilisée que dans les constructeurs (cf. section 5.1 page 29).

Tout comme en C++, une méthode en Objective-C peut accéder aux données d'instance de son objet déclencheur. L'éventuel this-> du C++ est remplacé par self->.

C++

Objective-C

class Toto

{ int x; int y; void f(void);

};

void Toto::f(void)

{

x     = 1;


//this->yint y; //crée une ambiguité avec

y     = 2; //utilise le y localthis->y = 3; //résout l'ambiguité }

@interface : NSObject

{ int x; int y;

}

-(void) f; @end

@implementation Toto

-(void) f

{

x     = 1;

int y; //crée une ambiguité avec

//self->y

y     = 2; //utilise le y localself->y = 3; //résout l'ambiguité

}

@end

Une fonction est une partie de code qui doit pouvoir être référencée, par exemple pour utiliser des pointeurs de fonctions, ou des foncteurs. De plus, si le nom de la fonction est un bon candidat pour jouer le rôle d'identifiant, il ne doit toutefois pas poser problème en cas de surcharge de ce nom. Les langages C++ et Objective-C sont antagonistes dans leur façon de différencier les prototypes. En effet, le premier se focalise sur les types des paramètres, et le second sur les étiquettes des paramètres.

En C++, deux fonctions de même nom peuvent être différenciées par le type des paramètres. Dans le cas des méthodes, l'option const est également déterminante.

C++

int f(int);

int f(float); //OK, float est différencié de int

class Toto

{ public:

int g(int);

int //OK, float est différencié de int int g(float) const; //OK, le const est discriminant

};

class Titi

{ public:

int g(int); //OK, on est dans Titi::, différenciable de Toto:: }

En Objective-C, les fonctions sont celles du C : elles ne peuvent être surchargées (à moins d'utiliser un compilateur compatible avec le C99, comme l'est gcc). En revanche, les méthodes (qui ont une syntaxe différente) sont différenciables à travers les étiquettes des paramètres.

Objective-C

int f(int);

int f(float); : fonctions C non surchargeables (sauf en C99)

@interface : NSObject

{

}

-(int) g:(int) x;

-(int) g:(float) x; //Erreur : cette méthode n'est pas différenciée // de la précédente (pas d'étiquette)

g:(int) x :(int) y;                                        //OK : il y a deux étiquettes anonymes

g:(int) x :(float) y; //Erreur : indifférenciable de la méthode //précédente

-(int) g:(int) x andY:(int) y; //OK : la deuxième étiquette est "andY" -(int) g:(int) x andY:(float) y; //Erreur : indifférenciable de la

//méthode précédente

-(int) g:(int) x andAlsoY:(int) y; //OK : la deuxième étiquette est

//"andAlsoY", différent de "andY"

@end

méthode de différenciation par les noms permet d'exprimer simplement quel est le nom « exact » de la fonction, comme expliqué ci-après.

@interface : NSObject {}

//le nom de cette méthode est "g" -(int) g;

//le nom de cette méthode est "g:" -(int) g:(float) x;

//le nom de cette méthode est "g::" -(int) g:(float) x :(float) y;

nom de cette méthode est "g:andY:" -(int) g:(float) x andY:(float) y;

//le nom de cette méthode est "g:andZ:"

-(int) g:(float) x andZ:(float) z @end

Comme on le voit, deux méthodes Objective-C ne sont pas différenciées par les types mais par les noms. C'est donc grâce à ce nom qu'on dispose de l'équivalent des « pointeurs de méthodes », appelés sélecteurs, dans la section 3.3.5 de la présente page.

En seules les méthodes ont cette syntaxe particulière utilisant parenthèses et étiquettes. Il n'est pas possible de déclarer des fonctions avec cette syntaxe. La notion de pointeur de fonction est la même en C qu'en Objective-C. C'est en revanche pour les pointeurs de méthodes qu'un problème se pose. La notion de pointeur de méthode n'est pas implémentée en Objective-C comme en C++.

En C++, il s'agit d'une syntaxe difficile, mais cohérente avec le C : un pointeur qui se focalise sur les types.

C++

class Toto

{ public:

int f(float x) {...}

};

Toto titi

int (Toto::*p_f)(float) = &Toto::f; //Pointeur vers Toto::f

(titi.*p_f)(1.2345); //appel de titi.f(1.2345);

En Objective-C, un nouveau type a été introduit. Ce « pointeur de méthode » s'appelle un sélecteur. Il est de type SEL et sa valeur est calculée par un appel à @selector sur le nom exact de la méthode (comprenant les étiquettes des paramètres). Déclencher la méthode peut se faire grâce à la classe NSInvocation. Dans la plupart des cas, les méthodes utilitaires de la famille performSelector (héritées de NSObject) sont plus pratiques, mais plus limitées. Les trois méthodes performSelector: les plus simples sont les suivantes :



-(id) performSelector:(SEL)unSelecteur;

-(id) performSelector:(SEL)unSelecteur withObject:(id)unObjetPourParametre; -(id) performSelector:(SEL)unSelecteur withObject:(id)unObjetPourParametre withObject:(id)unAutreObjetPourParametre;

La valeur renvoyée est celle de la méthode déclenchée. Pour des méthodes dont les paramètres ne sont pas des objets, il faudra songer à utiliser des classes d'encapsulation comme NSNumber, pouvant fournir des float, des int, etc. implicitement. On peut aussi se tourner vers la classe

NSInvocation, plus générale et plus puissante (voir la documentation).

D'après ce qui précède, rien n'empêche d'essayer de déclencher une méthode dans un objet dont la classe ne l'implémente pas. Mais le déclenchement ne découle que de l'acceptation du message. Une exception, interceptable, est levée dans le cas où la méthode à déclencher n'est pas connue par l'objet, mais le programme ne s'interrompt pas brutalement. En outre, on peut tester explicitement si un objet sait traiter une méthode via un appel à respondsToSelector:.

Enfin, la valeur de @selector() étant évaluée à la compilation, elle ne pénalise pas le temps d'exécution.

Objective-C

@interface Larbin : NSObject {}

@end

//Supposons un tableau tab[] de 10 larbins et un dossier "dossier"

//utilisation simple for(i=0 ; i<10 ; ++i)

[tab[i] lireDossier:dossier];

//simplement pour l'exemple, utilisation de performSelector:

for(i=0 ; i<10 ; ++i)

[tab[i] performSelector:@selector(lireDossier:) withObject:dossier];

//le type d'un sélecteur est SEL. La version ci-dessous n'est pas plus

//efficace que la précédente, car @selector() est évalué à la compilation SEL selecteur = @selector(lireDossier:);

; i<10 ; ++i)

[tab[i] performSelector:selecteur withObject:dossier];

//sur un objet "toto" de type inconnu (id), le test n'est pas //obligatoire mais empêche la levée d'exception si l'objet //n'a pas de méthode lireDossier:.

if ([toto respondsToSelector:@selector(lireDossier:)])

[toto performSelector:@selector(lireDossier:) withObject:dossier];

Un intérêt du sélecteur est être employé comme paramètre de fonction de façon extrêmement simple. Des algorithmes génériques, comme le tri, peuvent ainsi être aisément spécialisés (cf. 11.3 page 58).

Un sélecteur n'est cependant pas à proprement parler un pointeur de fonction; son type sousjacent réel est une chaîne de caractères, enregistrée auprès du run-time, identifiant une méthode. Lorsqu'une classe est chargée en mémoire, ses méthodes sont ainsi inscrites dans une table pour que fonctionne le mécanisme de @selector(). De cette façon, par exemple, l'égalité de deux sélecteurs est une égalité d'adresses, et non une égalité de chaîne de caractères; l'opérateur == peut donc être employé.

L'adresse réelle d'une méthode, en tant que fonction C, est obtenue par un mécanisme différent, présenté brièvement en section 11.3.2 page 58, avec le type IMP. Son utilisation est plus rare, et surtout liée à l'optimisation. La résolution des appels virtuels, par exemple, est gérée par les sélecteurs, mais pas par les IMP. L'équivalent Objective-C des pointeurs de méthodes du C++réside donc bien dans les sélecteurs.

Notez enfin que si self est en Objective-C, à l'instar de this en C++, un paramètre caché de chaque méthode représentant l'objet en cours, il en existe un deuxième, _cmd, qui est le sélecteur de la méthode courante.

Objective-C

@implementation Toto

-(void) f:(id)parametre //équivalent à une fonction C de type

//"void self, SEL _cmd, id parametre)" { id objetCourant = self;

SEL methodeCourante = true

[objetCourant performSelector:methodeCourante withObject:parametre]; //appel récursif [self performSelector:_cmd withObject:parametre]; //idem

}

@end

L'Objective-C ne permet pas de spécifier des valeur par défaut pour les paramètres. Il faut donc créer autant de fonctions que nécessaire lorsque plusieurs nombres d'arguments sont envisageables. Dans le cas des constructeurs, il faut se référer à la notion d'initialisateur désigné (section 5.1.7 page 33) pour respecter les canons.

L'Objective-C permet de spécifier des méthodes au nombre d'arguments variable. Tout comme en C, il s'agit de rajouter « ... » en dernier argument. C'est une pratique fort rare pour l'utilisateur, même si de nombreuses méthodes implémentées dans Cocoa le font. Le lecteur curieux peut consulter la documentation Objective-C pour de plus amples détails sur ce point.

En C++, il est possible de ne pas nommer tous les paramètres dans le prototype d'une fonction, puisque leur type suffit à caractériser la signature. En Objective-C, cela n'est pas possible.

En C++, un certain nombre de modificateurs peuvent être ajoutés aux prototypes des fonctions. Aucun d'eux n'existe en Objective-C. En voici la liste :

-   const : une méthode ne peut être spécifiée const. Le mot-clef mutable n'existe donc pas;

-   static : la différence entre méthode d'instance et méthode de classe se fait par l'utilisation du « - » ou du « + » devant le prototype;

-   virtual : les méthodes sont virtuelles en Objective-C, ce mot-clef est donc inutile. Les fonctions virtuelles pures s'implémentent par un protocole formel (cf. section 4.4 page 23);

-   friend : il n'y a pas de notion de classe ou méthode amie en Objective-C;

-   throw : en C++, on peut autoriser une méthode à ne transmettre que certaines exceptions.

En Objective-C, ce n'est pas le cas.

Par défaut, il est légal d'envoyer un message (appeler une méthode) sur nil. Le message est simplement ignoré. Cela permet de simplifier le code en diminuant fortement le nombre de tests nécessaires avec le pointeur nul. GCC dispose d'une option pour désactiver ce comportement pratique, à des fins d'optimisation.

La délégation est avec certains éléments d'interface graphiques de Cocoa (tableaux, arborescence...). Il s'agit d'exploiter le fait qu'il est possible d'envoyer des messages à un objet inconnu. Un objet peut se décharger de certaines tâches en exploitant un objet assistant.

//Supposons l'existence d'une fonction d'attribution d'assistant

-(void) setStagiaire:(id)esclave

{

[stagiaire autorelease]; //voir la section sur la gestion mémoire stagiaire = [esclave retain];

}

//la méthode faireUnTrucPenible peut implémenter une délégation

-(void) faireUnTrucPenible:(id) truc

{

//le stagiaire est inconnu.

//On vérifie qu'il peut traiter le message

if ([stagiaire respondsToSelector:@(faireUnTrucPenible:)])

[stagiaire faireUnTrucPenible:truc]; else

[self changerDeStagiaire]; }

En C++, on ne peut pas à la compilation forcer un objet à essayer d'exécuter une méthode qu'il n'implémente pas. En Objective-C, ce n'est pas pareil : on peut toujours envoyer un message à un objet. S'il ne peut pas le traiter, il l'ignorera (en levant une exception); ou mieux : plutôt que de l'ignorer, il peut le retransmettre à un tiers.

Notez que lorsque le compilateur détecte un envoi de message à un objet, et que d'après son type cet objet ne connaît pas le message, un avertissement est levé. Ce n'est cependant pas une erreur, car lorsqu'un objet reçoit un message qu'il ne sait pas traiter, une seconde chance lui est offerte. Cette seconde chance prend forme par un appel automatique à la méthode forwardInvocation:, qui peut être surchargée pour re-router le message au dernier moment. C'est bien sûr une méthode de NSObject qui par défaut ne fait rien. C'est encore une façon de gérer des objets assistants.


505