Chose promise, chose due ! Je vais vous raconter la fabuleuse histoire des optionnels ! Et nous allons pouvoir modifier notre programme grâce à eux.
Suivez une histoire de licornes…
Il était une fois un dictionnaire (toutes les bonnes histoires commencent par Il était une fois, non ?). Ce dictionnaire contenait le nombre de licornes se trouvant dans chaque pays, comme ceci :
var nombreDeLicornes = ["France": 165, "USA": 38, "Italie": 102]
Très heureux de sa mission, notre dictionnaire coulait des jours heureux. Dans son travail, les programmes lui demandaient toujours des informations qu’il pouvait donner. Par exemple, on lui disait : “Donne-moi le nombre de licornes en France”. Et avec plaisir, notre ami dictionnaire s’exécutait.
nombreDeLicornes["France"] // Il renvoyait 165
Mais un jour, un programme vicieux le piégea : « Donne-moi le nombre de licornes au Groenland ».
Notre dictionnaire pris de panique par cette demande tenta l’impossible avec cette instruction :
nombreDeLicornes["Groenland"]
Mais bien sûr cette commande ne retourna rien, et le programme planta.
Personne ne se maria ni n’eut beaucoup d’enfants.
THE END
Bon OK, c’était une histoire tragique… Mais dans la vie d’un programmeur, tout n’est pas rose. Autant vous y faire tout de suite !
C’est après avoir été traumatisé pendant 50 ans par cette histoire que les créateurs de Swift ont fini par inventer les optionnels ! Vous pouvez déjà les remercier.
Parce qu’en fait, cette histoire ne se limite pas aux dictionnaires. Par exemple, je suis sûr que vous vous êtes déjà inscrit sur de nombreux sites web qui demandaient à ce que vous remplissiez un paquet d’informations. Parmi celles-ci, il y en avait qui étaient obligatoires, comme votre e-mail ou votre mot de passe. Mais souvent on vous propose d’en remplir d’autres facultatives, comme votre adresse ou votre téléphone. Et si vous êtes comme moi, vous ne les remplissez pas.
Le problème, c’est qu’un programmeur innocent comptait peut-être sur vous pour remplir votre numéro de téléphone pour pouvoir l’afficher sur l’application ou pour vous envoyer un SMS. Et donc quelque part dans son programme, il a une variable téléphone
qui ne contient aucune valeur !
var telephone = // Rien !
Et si jamais il essaie d’utiliser cette variable sans valeur (ou autrement dit cette variable non initialisée), son programme va planter, comme vous le savez.
D’où les optionnels !
Découvrez les optionnels, concrètement !
MAIS C’EST QUOI ???
OK, j’ai peut-être fait un peu durer le suspens… En fait, un optionnel c’est un type un peu spécial. C’est le seul type qui autorise une variable à être utilisée sans valeur. Vous vous souvenez du cycle de vie d’une variable :
La variable est déclarée. À ce moment, elle a un nom et un type, et on sait si elle est constante ou non. Comme la variable n’a pas de valeur à ce moment-là, elle ne peut pas être utilisée.
La variable est initialisée. Elle a une valeur. La variable peut être utilisée.
On ne peut donc pas utiliser une variable qui n’a pas été initialisée. Mais avec les optionnels, si ! Car ils autorisent une variable à ne pas avoir de valeur.
On va voir tout de suite à quoi ça ressemble. Pour déclarer un optionnel, on utilise le point d’interrogation ?
à la suite du type. Comme ceci :
var jeSuisOptionnel: Int? // Ceci est un optionnel
var jeNeSuisPasOptionnel: Int // Ceci est un entier
Les deux variables ci-dessus ne sont pas initialisées. Elles n’ont pas de valeur. Essayons de les utiliser :
var jeSuisOptionnel: Int? // Ceci est un optionnel
jeSuisOptionnel // Le Playground affiche nil
var jeNeSuisPasOptionnel: Int // Ceci est un entier
jeNeSuisPasOptionnel // Le Playground plante…
Pour la variable classique, le programme plante, car on essaie d’accéder à la valeur d’une variable qui n’en a pas ! C’est interdit par Swift. Mais c’est autorisé pour les optionnels. Et donc pour la variable jeSuisOptionnel
, le programme renvoie nil
.
nil ?
nil
en Swift, c’est un mot clé qui veut dire nada / rien / que dalle / zip / pas de valeur. Et c’est bien le cas, notre variable jeSuisOptionnel
ne contient rien.
Et à quoi ressemble un optionnel qui a une valeur ? Essayons !
var jeSuisOptionnel: Int? = 12
print(jeSuisOptionnel)
Dans la console vous devriez voir :
Optional(12)
Il y a bien écrit Optional(12)
et pas 12
!
Pourquoi ?
Pour une raison très simple, mais cruciale pour que vous compreniez correctement les optionnels.
La variable jeSuisOptionnel
N’EST PAS DU TYPE Int
MAIS DU TYPE Optional
. Je répète : La variable jeSuisOptionnel
N’EST PAS DU TYPE Int
MAIS DU TYPE Optional
.
Pour être sûr que le message soit bien passé, je vais vous faire un petit cadeau.
Un optionnel, c’est un paquet cadeau
Eh oui, un optionnel c’est comme un paquet cadeau qui englobe une valeur. Si on vous offre un paquet cadeau, vous ne savez pas ce qu’il y a dedans avant de l’ouvrir. Plus précisément, à l’intérieur :
Soit il n’y a rien. Quelqu’un vous a fait une mauvaise blague…
Soit il y a quelque chose mais vous ne savez pas quoi avant de l’ouvrir. Joyeux anniversaire !
Un optionnel se comporte exactement comme l’explique le schéma suivant.
Dans le schéma précédent, x
est un optionnel et non une string ! Le type String?
ne doit pas se lire : « Je suis peut-être une string » mais « Je suis un optionnel qui contient peut-être une string ».
Déballez le cadeau
Les optionnels sont bien pratiques pour gérer des variables qui peuvent ne pas avoir de valeur. Mais alors comment accède-t-on à leur valeur ? Il existe 6 méthodes :
2 méthodes sécurisées ;
3 méthodes alternatives ;
1 méthode non sécurisée.
Première méthode sécurisée et recommandée : la déclaration optionnelle avec if let
La première méthode sécurisée est plus subtile, mais aussi plus pratique ! C’est la raison pour laquelle c’est aussi la plus utilisée ! Elle s’utilise comme ceci :
if let variableDéballée = variableOptionnelle {
print("La variable déballée vaut \(variableDéballée)")
}
Je vais détailler ce qu’il se passe ici :
Le programme regarde si la variable
variableOptionnelle
vautnil
.Si elle ne contient pas nil, le programme crée une variable constante nommée
variableDéballée
.La valeur de la variable déballée est attribuée à la nouvelle constante
variableDéballée
.
L’avantage de cette méthode, c’est que la variable nouvellement créée n’est pas de type optionnel. C’est une variable classique qu’on peut donc utiliser normalement. On appelle cela une déclaration optionnelle (ou optional binding, en anglais). C’est très courant en Swift.
Un jour vous repenserez à ce jour où vous n’utilisiez pas if let, et vous aurez un sourire nostalgique (éventuellement en pensant à votre prof !).
Deuxième méthode sécurisée et recommandée : la déclaration optionnelle avec guard let / else
Swift nous donne une alternative à la première méthode sécurisée appelée guard let
avec `else`
, qui déballe également les optionnels s'ils contiennent une valeur. Mais cette méthode fonctionne légèrement différemment : guard let
est conçu pour quitter la fonction, la boucle ou la condition actuelle si la vérification échoue. Toutes les valeurs que vous déballerez en l'utilisant persisteront donc même en dehors du bloc de code (aussi appelé “scope”) servant au déballage de l’optionnel.
Cette méthode est aussi très populaire ! :)
Kézako ? Mais c’est quoi la différence entre les deux méthodes, du coup ?
Pour résumer :
Avec
guard let
/else
, vous créez une nouvelle variable qui existera toujours en dehors de l'instructionelse
.Avec
if let
, vous ne créez pas de nouvelle variable. Le déballage se fait uniquement si l’optionnel a une valeur différente denil
. La variable nouvellement créée n'existe qu'à l'intérieur du bloc de code mais plus après ! ;)
Pour vous démontrer la différence entre ces 2 méthodes sécurisées, voici un exemple. Prenons une fonction qui renvoie la réponse sur la grande question sur la vie sous la forme d'un entier facultatif :
func reponseUniverselle() -> Int? {
42
}
Et voici une autre fonction qui va nous permettre de l’afficher :
func afficherReponseUniverselle() -> Int? {
if let nom = reponseUniverselle() {
print(nom)
return nom
}
return nil
}
Au-dessus, on utilise la méthode sécurisée avec if let
. Cela signifie que le résultat de la fonction ne sera affiché que s'il a renvoyé un entier ( Int
) plutôt que nil
.
Si nous avions écrit cela en utilisant la méthode sécurisée avec guard let
/ else
, cela ressemblerait à ceci :
func afficherReponseUniverselle2() -> Int? {
guard let nom = reponseUniverselle() else {
return nil
}
print(nom)
return nom
}
Il est courant de voir guard let
, car il est utile pour vérifier que les conditions sont correctes dès le départ. Cela rend notre code plus facile à lire que si nous essayions de vérifier une condition, puis d'exécuter du code, puis de vérifier une autre condition et d'exécuter un code différent.
Donc, utilisez if let
si vous voulez juste déballer quelques optionnels, mais préférez guard let
si vous vérifiez spécifiquement que les conditions sont correctes avant de continuer.
Première méthode alternative : la vérification avant utilisation
Non recommandée, cette méthode est dangereuse, car il faut être certain que la variable ait une valeur pour utiliser le point d’exclamation.
Pour pallier ce risque, il existe deux moyens pour s’assurer que la variable a une valeur. Le premier est assez intuitif :
if jeSuisUnOptionnel != nil {
print("La variable contient une valeur")
}
Ici, on vérifie que l’optionnel ne vaut pas nil. Si c’est le cas, on peut donc le déballer avec le point d'exclamation :
if jeSuisUnOptionnel != nil {
print("La variable vaut \(jeSuisUnOptionnel!)")
}
Deuxième méthode alternative : opérateur ternaire
Non recommandée, cette méthode alternative consiste à utiliser un opérateur ternaire.
Kézako ?! Terna… quoi ???
Encore un nom compliqué mais rassurez-vous ! Vous allez voir que le concept est très simple ! ;)
Au fait, il ne s’agit ni plus ni moins que d’un raccourci avec une instruction if / else.
Par exemple, si l’on écrit :
if question {
réponse1
} else {
réponse2
}
Avec un opérateur ternaire, on va pouvoir simplifier la syntaxe comme cela :
question ? réponse 1 : réponse 2
On peut appliquer cette formule pour notre exemple :
var jeSuisOptionnel: Int? = 12
let valeurDéballée = jeSuisOptionnel != nil ? jeSuisOptionnel! : 0
C’est beaucoup mieux parce qu’au final on a réussi à réduire nos 5 lignes de code en une seule ! Si jeSuisOptionnel
a une valeur, alors on déballe l’optionnel, sinon notre valeur par défaut 0
est utilisée.
Troisième méthode alternative : opérateur nil-coalescing
Comme vous l’aurez compris, les optionnels sont bien pratiques pour gérer des variables qui peuvent ne pas avoir de valeur. Cependant, il arrive parfois que l’on ai envie d’écrire une valeur facultative et une valeur par défaut en cas d’absence de valeur, mais qui serait non nil ! Pour cela, il existe l’opérateur nil-coalescing.
La syntaxe est beaucoup plus simple à utiliser que l’opérateur ternaire. Reprenons notre exemple :
var jeSuisOptionnel: Int? = 12
let valeurDéballée = jeSuisOptionnel ?? 0
jeSuisOptionnel
est de type optionnel, et il peut peut-être contenir un Int
. Avec l’opérateur nil-coalescing ??
, on va pouvoir être sûr que valeurDéballée
va contenir une valeur autre que nil, en utilisant la valeur par défaut, celle à droite. Ici, la valeur par défaut est donc 0
. Ce qui signifie que la variable valeurDéballée
deviendra un Int
quoi qu’il arrive. Et ça, c’est plutôt pratique !
Par exemple, vous n’avez pas besoin de créer des variables séparées pour utiliser un opérateur nil-coalescing :
print(“Combien reste-t-il d’argent à Joe ? Il lui reste \(jeSuisOptionnel ?? 0) euros”)
La méthode non-sécurisée et dangereuse : déballage forcé
Pour déballer un optionnel, on peut utiliser le point d’exclamation ! à la fin de la variable comme ceci :
var jeSuisOptionnel: Int? = 12
jeSuisOptionnel! // La variable est déballée avec le point d’exclamation
Si on essaye d’écrire print(jeSuisOptionnel!)
, la console affichera directement 12
et non Optional(12)
. L’optionnel a été déballé. C’est comme si on utilisait directement une variable non optionnelle.
Le problème, c’est que du coup si l’optionnel n’a pas de valeur, le programme va planter.
var jeSuisVide: Int?
jeSuisVide! // Le programme plante car la valeur est nil
Appréhendez quelques usages des optionnels
Les optionnels au début, ça peut être un peu déstabilisant. Voilà donc quelques exemples d’utilisation des optionnels pour que vous sentiez leur intérêt.
La conversion de String
en Int
On a vu que l’on pouvait transformer un type en un autre en utilisant l’initialisation du type. Par exemple, comme ceci :
Int(3.4) // Renvoie 3
On a converti 3.4 de type double, en 3 qui est de type Int, grâce à l’instruction Int. Avec cette méthode on peut aussi convertir un type String
en type Int
. Comme ceci :
var a = Int("123")
Et si l’on écrit print(a)
, la console affiche Optional(123)
. Eh oui ! a
est un optionnel ! Pourquoi ? Car pour transformer une String
en Int
, il faut que cette string contienne uniquement des chiffres. Par exemple si j’écris :
var a = Int("Des pommes")
L’initialisation ne va pas fonctionner, car on ne peut pas convertir la chaîne "Des pommes" en entier, et a
va valoir nil
. Donc cette instruction permettant de créer un Int
à partir d’une String
renvoie :
Soit un entier si la string utilisée est convertible.
Soit nil si la string n’est pas convertible en Int.
Et c’est bien la définition d’un optionnel !
Les propriétés first et last
Pour rappel, pour accéder aux données d’un tableau, on utilise son index. Par exemple :
var poupeesRusses = ["petite", "moyenne", "grande"]
poupeesRusses[1] // Moyenne
Pour accéder à la première valeur de ce tableau, on ferait : poupeesRusses[0]
, et pour la dernière valeur on peut faire poupeesRusses[2]
. Ça, vous le savez. Mais sachez que Swift propose des propriétés bien pratiques pour ces deux cas : les propriétés first
et last
:
var petite = poupeesRusses.first
var grande = poupeesRusses.last
Cela permet d’avoir un programme qui se lit plus clairement. Et c’est plus pratique.
Seulement, si le tableau est vide, les propriétés first
et last
ne peuvent rien renvoyer. Donc elles renvoient nil. Du coup, les propriétés first
et last
peuvent renvoyer :
soit un élément du tableau ;
soit nil si le tableau est vide.
C’est donc un optionnel. Vous pouvez le tester en écrivant print(grande)
qui affiche Optional("grande")
.
Les dictionnaires
Pour accéder à une valeur dans un dictionnaire, on utilise sa clé comme ceci :
var barn = ["milk": 0, "wheat": 0, "wool": 0]
barn["milk"] // Renvoie 0
Ça, vous le savez. Mais je vous ai menti. Et l’heure de vérité est arrivée ! En utilisant la clé, le dictionnaire ne renvoie pas directement la valeur mais un optionnel. Essayez d’écrire par exemple :
print(barn["milk"])
La console affiche Optional(0)
. Pourquoi ne pas renvoyer la valeur directement, me direz-vous ? Car si l’on se trompe de clé, le dictionnaire ne peut rien renvoyer (cf. la fameuse histoire de licornes…). Par exemple, si j’écris :
print(barn["MILK"])
Le dictionnaire ne contient aucune valeur associée à la clé MILK
. Et donc il va renvoyer nil
. Autrement dit, lorsque je cherche à accéder à un élément dans un dictionnaire, le dictionnaire renvoie :
Une valeur si la clé existe.
Rien si la clé n’existe pas.
Donc il est normal que le dictionnaire renvoie un optionnel ! Donc pour effectivement accéder à sa valeur, il faut le déballer.
À vous de jouer
Le contenu de l’exercice se trouve dans le dossier Github P3C3.
Ouvrez un nouveau Playground Xcode.
Copiez le contenu du fichier “main.swift” dans votre Playground.
Suivez les instructions.
Ici, j’utilise la première méthode sécurisée avec le if let
pour déballer les valeurs de mon dictionnaire. On notera dans la correction que je déballe volontairement sur une ligne les optionnels avec une virgule (,) afin de faciliter la lecture du code pour gagner en clarté ! ;).
En résumé
Un optionnel est un type qui permet à une variable d’être utilisée sans valeur.
Dans le cas où l’optionnel ne contient pas de valeur, on dit que sa valeur est
nil
.Il faut se représenter un optionnel comme un paquet cadeau qui contient :
soit une valeur du type indiqué ;
soit rien.
Un optionnel doit donc être déballé, selon de différentes méthodes :
Une première méthode sécurisée et recommandée qui permet d’accéder à la variable par la création d’une nouvelle variable.
Une deuxième méthode sécurisée et recommandée qui permet d’accéder à la variable par la création d’une nouvelle variable, et qui sera réutilisable en dehors de la scope.
D'autres méthodes existent, mais ne sont pas recommandées.
Lorsqu’on accède aux valeurs d’un dictionnaire avec une clé, le dictionnaire renvoie un optionnel et non la valeur directement.
Maintenant que vous savez tout sur tout avec les optionnels, que direz-vous si je vous dis que dans le chapitre suivant je vais vous apprendre à créer vos premières fonctions ? ;)
Mais avant cela, je vous invite à réaliser un petit quiz afin de vérifier vos nouvelles connaissances ! :p