IV. Initialiser classes et objets▲
Nous avons vu comment déclarer une classe et comment préparer l'implémentation des méthodes qui seront associées aux messages acceptés par la classe.
Pour aller plus loin, il faut être capable de construire proprement des instances d'une classe, et si besoin initialiser les variables globales utilisées par une classe.
Cette partie a donc pour but de détailler :
- l'initialisation d'une classe ;
- le mécanisme de construction d'un objet ;
- la fin de vie d'un objet.
IV-A. Initialisation de la classe▲
Les classes ont parfois besoin d'une phase d'initialisation qui leur soit propre. Ceci est particulièrement vrai si l'on utilise des variables de classe.
Java propose un mécanisme simple pour initialiser les membres d'une classe : l'initialisation au moment de la déclaration :
class
UneClasse {
// Une constante
static
public
final
String LABEL =
"unLabel"
;
// Un objet
static
private
java.net.URL baseURL =
new
java.net.URL (
"http://www.google.com/"
) ;
}
Cette méthode, si elle fonctionne très bien pose un problème pour la création d'objets plus complexes. Dans le cas de la construction de la variable baseURL, on se trouve dans un cas où une solution doit être trouvée pour pouvoir gérer les cas d'erreurs.
En effet, la construction de l'URL peut générer une exception qui ne sera pas traitée par la classe, mais qui sera interceptée par le chargeur de classe (ClassLoader). Pour peu que la classe ne soit pas initialisée explicitement, une telle erreur peut être ensuite plus délicate à identifier correctement.
Une bien meilleure solution est proposée en Java : l'initialisation dans le bloc static.
class
UneClasse {
// Une constante
static
public
final
String LABEL =
"unLabel"
;
// Un objet non initialisé
static
private
java.net.URL baseURL;
// Initialisation
static
{
try
{
baseURL =
new
java.net.URL (
"http://www.google.com/"
) ;
}
catch
(
final
Throwable t ) {
// ... Je peux gérer les erreurs
}
}
}
Objective-C propose un système tout à fait comparable, mais sans introduire de syntaxe particulière.
Comme dans Objective-C les classes sont des objets comme les autres, l'objet associé à une classe peut être initialisé en implémentant une méthode initialize.
La déclaration se fait dans le fichier d'interface :
@interface
UneClasse : NSObject
{
}
+
(
void
) initialize; // Initialise les membres de classe
@end
L'implémentation est localisée avec celle des autres méthodes :
static
NSURL
baseURL;
@implementation
UneClasse
+
(
void
) initialize
{
baseURL =
[NSURL
URLWithString: "http://www.google.com/"
];
}
@end
Il est important de remarquer que dans un cas comme dans l'autre l'initialisation des membres de classe est appelée pour chaque classe chargée en mémoire. Il ne faut donc pas appeler la méthode initialize de la classe parente.
Les états que peut prendre une classe sont présentés dans le diagramme d'états ci-dessous :
Pour résumer :
Java |
Objective-C |
|
---|---|---|
Principe |
Construction Syntaxique |
Méthode de classe |
Initialisation |
static { ... } |
+initialize ; |
IV-B. Construction d'instance▲
En Java comme en Objective-C les instances d'une classe sont construites en deux étapes :
- Allocation de la zone mémoire contenant l'objet ;
- Initialisation des valeurs des membres de l'objet.
La syntaxe Java est la suivante :
String chaine =
new
String
(
"une chaine"
);
L'opérateur new masque les opérations d'allocation et d'initialisation en une seule étape. C'est la syntaxe de Java qui oblige simplement d'indiquer quel constructeur va être utilisé pour initialiser l'objet. L'opérateur new alloue automatiquement la zone mémoire avant d'appeler le constructeur.
Dans le respect de la philosophie d'Objective-C, le langage n'ajoute aucune structure syntaxique pour cette opération. Les deux opérations sont explicitement visibles dans le code source en deux parties :
- L'appel d'allocation, envoyé à la classe ;
- L'appel d'initialisation, envoyé à la nouvelle instance.
Les méthodes appelées sont :
- +alloc pour allouer la zone mémoire ;
- Par convention, l'objet doit répondre à un message init pour s'initialiser. Mais plusieurs variantes sont possibles (6)
La syntaxe normale pour créer une instance est la suivante : (7)
NSString
*
chaine =
[NSString
alloc];
[chaine initWithCString:"une chaine"
encoding
:
NSISOLatin1StringEncoding
];
Il est bien évident que cette syntaxe est un peu trop longue. La construction est généralement compactée comme suit :
NSString
*
chaine =
[NSString
alloc] initWithCString:"une chaine"
encoding
:
NSISOLatin1StringEncoding
]];
La déclaration des constructeurs se fait dans le fichier d'en-tête .h :
@interface
UneClasse : NSObject
{
}
-
(
id
) init;
@end
Pour sa part l'implémentation est logiquement dans le fichier .m :
@implementation
UneClasse
-
(
id
) init
{
[super
init]; // Il faut initialiser l'objet parent!
// Mes initialisations spécifiques ci-dessous
// ...
}
@end
La solution proposée par Objective-C a l'avantage de la simplicité syntaxique en limitant les extensions imposées au C. C'est aussi un moyen très simple pour une classe de suivre les créations d'instances puisqu'il est tout à fait possible de surcharger la méthode alloc.
Si la chose est tout à fait possible en Java, elle doit cependant être réalisée dans le constructeur, ce qui peut malheureusement être surchargé. S'ajoute également l'inconvénient de mélanger un code spécifiquement lié au fonctionnement de la classe avec du code spécifique aux instances.
Java |
Objective-C |
|
---|---|---|
Principe |
Opérateur et méthode d'instance spéciale |
Méthode de classe et méthodes d'instance |
Allocation |
new UneClasse(...) ; |
[UneClasse alloc] |
Initialisation |
UneClasse() |
- init |
Initialisation |
UneClasse(p1, p2) |
- initFromParam : p1 andParam : p2 |
Un objet suit les évolutions suivantes :
- allocation
- initialisation
- utilisation
- libération
Ce qui est résumé dans le diagramme d'état suivant :
IV-C. Destruction d'une instance▲
Lorsqu'un objet devient inutile, il est souhaitable de le supprimer pour récupérer l'espace mémoire qui est lui associé.
En Java la gestion de la mémoire est quasi transparente du fait de la machine virtuelle qui gère tout avec un système de ramasse-miettes (garbage collection).
La situation est très similaire avec Cocoa. Dans sa version actuelle, le ramasse-miettes n'est pas implémenté, mais il le sera dans la prochaine version disponible avec Léopard (Mac OS X.5). C'est donc au développeur de gérer la mémoire dans son application. Bien que simple, cette gestion de la mémoire fera l'objet d'un article spécifique.
Mais même avec un ramasse-miettes Java permet au développeur de faire le ménage dans ses objets. C'est la méthode finalize qui s'en charge :
protected
void
finalize
(
) throws
Throwable
Cette méthode est appelée par le ramasse-miettes et doit donc se contenter de vider ses collections ou remettre les références vers des objets tiers à null.
protected
void
finalize
(
) throws
Throwable {
// Mon ménage ci-dessous
// ...
// Le ménage pour la partie de la classe parente.
super
.finalize
(
);
}
C'est exactement le même principe en Objective-C.
Pour faire le ménage derrière elle, une classe doit proposer une implémentation de la méthode dealloc. Et comme en Java, la méthode doit faire suivre l'appel vers sa classe parente.
-
(
void
) dealloc {
// Mon ménage ci-dessous
// ... release des références d'objet
// ... mise à nil des références facultatives, mais utiles
// Le ménage pour la partie de la classe parente.
[super
dealloc];
}
Il est important de noter que cette méthode peut ne pas être appelée lorsqu'on quitte une application. Dans ce cas, il est plus efficace de laisser le système d'exploitation libérer la mémoire allouée par l'application en un seul bloc, et c'est ce que fait le runtime Objective-C.