Nous avons vu dans les chapitres précédents que l'on peut initialiser une classe en utilisant le mot-clé init
. Dans l'initialisation, on peut initialiser des propriétés et faire toutes sortes de calculs pour préparer notre objet comme on le souhaite. Dans ce chapitre, nous allons voir que l'initialisation obéit à un certain nombre de règles que l'on va apprendre à respecter.
Découvrez la première règle
Si vous ne retenez qu'une seule chose de ce chapitre, retenez la première règle ! La première règle de l'initialisation est la suivante :
À la fin de l'initialisation, toutes les propriétés stockées doivent être initialisées.
On peut se le refaire encore une fois, ça vaut le coup :
À la fin de l'initialisation, toutes les propriétés stockées doivent être initialisées.
Bien, qu'est-ce que ça veut dire ? Cela veut dire que lorsque l'initialisation est terminée, toutes les propriétés stockées doivent avoir une valeur. Et pour faire cela, vous savez déjà comment faire. Vous avez trois options :
soit vous déclarez votre propriété avec une valeur par défaut ;
soit vous déclarez votre propriété de type optionnel, elle aura pour valeur
nil
par défaut ;soit vous initialisez votre propriété dans l'initialiseur.
Cela veut dire que la première mission d'un initialiseur, c'est d'initialiser toutes les propriétés stockées qui n'ont pas de valeur. S’il ne remplit pas cette mission, votre programme va planter !
Appréhendez l’initialisation pratique et désignée
ll existe deux types d’initialiseurs. Jusqu'à présent, tous les initialiseurs que vous avez rencontrés sont du type désigné (designated). Les initialiseurs désignés sont les initialiseurs principaux d'une classe.
Nous allons découvrir maintenant un deuxième type d'initialiseur : les initialiseurs pratiques (convenience). Comme leur nom l'indique, ces initialiseurs ne sont pas primordiaux pour la classe, mais ils sont pratiques. Leur rôle est de proposer une initialisation facile, ou en tout cas plus facile que les initialisations prévues par les initialiseurs désignés.
Prenons tout de suite un exemple dans notre programme. Reprenons notre classe HomeRoadSection
:
class HomeRoadSection: RoadSection {
var children: Int
init(children: Int) {
self.children = children
super.init(type: .home)
}
}
Cette classe définit un initialiseur qui a en paramètre children
. C'est pratique. Mais parfois, nous n'allons pas avoir envie de préciser le nombre d'enfants dans la maison. Parfois on voudrait juste lui mettre une valeur par défaut. Nous allons donc définir un initialiseur pratique pour cela.
L'initialiseur pratique se déclare avec le mot-clé convenience
, comme ceci :
convenience init() {
self.init(children: 2)
}
Le mot-clé convenience permet d'indiquer la nature pratique de l'initialiseur. À l'intérieur de cette fonction, j'utilise l'initialisation désignée avec children
en paramètre. Maintenant, je peux écrire HomeRoadSection()
pour initialiser une nouvelle section. C'est plus concis et plus facile comme ça.
Il y a donc une différence d'intention entre les deux types d'initialiseurs :
désigné : ce sont les initialisations principales ;
pratique : ils permettent des initialisations faciles pour certains cas courants.
Faites la différence entre l’initialisation pratique et désignée
Mais derrière cette différence d'intention se trouve une vraie réalité technique ! Cette réalité peut s'illustrer en deux règles :
Un initialiseur désigné doit faire appel à un initialiseur désigné d'une classe mère si existante.
Un initialiseur pratique doit faire appel à un initialiseur désigné de la même classe.
Autrement dit, dans un initialiseur désigné, on doit trouver quelque part super.init
: on appelle l'initialiseur désigné de la classe mère. Et dans un initialiseur pratique, on doit pouvoir lire self.init
: on appelle un initialiseur désigné de la même classe.
Pour bien comprendre tout ça, réessayons de créer un initialiseur pratique. Cette fois, faisons-le avec la classe RoadSection
. On va pouvoir l'initialiser sans paramètre, et obtenir que le canevas dessine une section de route simple. Pour cela, nous allons créer l'initialiseur pratique suivant :
convenience init() {
self.init(type: .plain)
}
Cet initialiseur est bien du type pratique puisque je le déclare avec le mot-clé convenience
. Et il appelle l'initialiseur désigné de la même classe.
Pour bien comprendre tout ça, voici un schéma de l'initialisation de RoadSection
et HomeRoadSection
:
On voit bien dans ce schéma que les initialisations pratiques appellent les initialiseurs désignés de leur classe de façon horizontale. Tandis que les initialiseurs désignés appellent les initialiseurs désignés de la classe mère de façon verticale. Vous pouvez imaginer les initialiseurs désignés comme le tronc de l'initialisation de votre arbre de classe. Les initialisations pratiques n'en sont que les branches.
Vous verrez que dans le développement d'applications iPhone, vous serez souvent amené à créer des sous-classes de certaines classes proposées dans iOS. Vos sous-classes seront bien plus faciles à utiliser si vous maîtrisez les deux types d'initialisation.
Et la désinitialisation ?
Nous avions vu dans la partie 2 chapitre 3 que nous pouvions désinitialiser notre classe en utilisant le mot clé deinit
.
Pour lui, il n’y a pas de notion de désigné ou de pratique, car vous n’aurez qu’une seule méthode au nom de deinit
: soit celle par défaut utilisée par Swift, soit celle que vous fournirez à la classe. Pour rappel, deinit
ne prend pas de paramètres, c’est pour cela qu’il ne peut y en avoir qu’une seule version.
À vous de jouer !
Le contenu de l’exercice se trouve dans le dossier Github P4C2.
Ouvrez un nouveau Playground Xcode.
Copiez le contenu du fichier “main.swift” dans votre Playground.
Suivez les instructions.
En résumé
À la fin de l'initialisation, toutes les propriétés stockées doivent être initialisées.
Il existe deux types d'initialiseurs :
désignés : ce sont les initialisations principales ;
pratiques : ils permettent des initialisations faciles pour certains cas courants.
Un initialiseur désigné doit faire appel à un initialiseur désigné d'une classe mère si existante. Un initialiseur pratique doit faire appel à un initialiseur désigné de la même classe.
Maintenant que vous maîtrisez l’initialisation et ses mystères, regardons ensemble comment protéger vos classes !