Fiche 00.02 — Les optionals en profondeur
Objectif
Maîtriser les optionals Swift : ce qu'ils sont vraiment (un enum), comment les déballer proprement avec if let / guard let, et virer le réflexe ! hérité d'Objective-C. C'est la base qui distingue un dev Swift propre d'un dev qui crashe en prod.
1. Un optional, c'est juste un enum
En Objective-C, n'importe quel objet pouvait être nil silencieusement. Tu envoyais un message à nil, il ne se passait rien (ou un crash plus loin, sans raison apparente). Swift refuse ce flou : un type non optionnel ne peut jamais être nil, point.
Optional<T> est littéralement un enum de la lib standard :
String? n'est que du sucre syntaxique pour Optional<String>. nil est .none. Une valeur est .some(valeur).
Pont UIKit : en ObjC un NSString *name pouvait toujours être nil sans que le compilateur bronche. Ici, si le type est String (sans ?), le compilateur garantit qu'il y a une valeur. L'absence devient explicite dans le type, pas une surprise à l'exécution.
2. if let — déballer dans une branche
Le pattern de base : tu déballes, et tu n'as accès à la valeur que dans le bloc.
Avant Swift 5.7 tu écrivais if let name = name { ... }. Le shorthand if let name est devenu le standard quand le nom de la variable déballée est le même.
Tu peux enchaîner plusieurs déballages et ajouter des conditions :
3. guard let — déballer puis continuer (early return)
if let imbrique vers la droite (pyramid of doom). guard let fait l'inverse : il sort tôt si la valeur est absente, et la variable déballée reste disponible dans tout le reste de la fonction.
Cas réel courant : la forme courte guard let self (Swift 5.7+) dans une closure, pour éviter le guard let self = self else { return } verbeux :
Le else d'un guard doit obligatoirement transférer le contrôle hors du scope : return, throw, break, continue ou fatalError(). Le compilateur le force.
4. ?? nil-coalescing — une valeur par défaut
Quand tu veux juste une valeur de repli, pas une branche :
?? court-circuite : la partie droite n'est évaluée que si la gauche est nil. Tu peux chaîner :
Pas idéal — utiliser if let juste pour fournir un défaut :
Préférable :
5. ?. optional chaining — appeler sans déballer
Tu accèdes à une propriété ou méthode sur un optionnel : si l'optionnel est nil, toute l'expression vaut nil (et le résultat est lui-même optionnel).
Pont UIKit : exactement le comportement ObjC d'« envoyer un message à nil sans crash », mais ici c'est explicite et typé — le résultat est marqué ?, tu sais que tu dois encore le gérer.
Combiné avec ?? c'est ultra fréquent :
6. map / flatMap sur Optional
Un optionnel est un conteneur, donc il a map : transformer la valeur si elle existe, sinon rester nil. Évite un if let pour une simple transformation.
Règle simple : si ta closure renvoie un non-optionnel, prends map. Si elle renvoie déjà un optionnel (comme Int(String) qui peut échouer), prends flatMap pour éviter le Int??.
7. Force unwrap ! — rare et risqué
valeur! déballe brutalement : si c'est nil, crash immédiat (fatalError). C'est l'équivalent moderne du nil silencieux d'ObjC… sauf qu'ici ça plante fort au lieu de pourrir tes données en douce.
Acceptable dans de rares cas où l'absence est un bug de programmeur, pas un état runtime possible :
Dangereux dès que la valeur vient de l'extérieur (réseau, JSON, saisie utilisateur, Optional d'une API système) :
Préférable — déballe proprement et gère l'absence :
Astuce crédibilité : array.first! se réécrit souvent sans optionnel du tout. Pour un défaut, ??. Pour une garantie, guard.
8. Optionnels imbriqués, try?, as?
Optionnels imbriqués (Int??) arrivent surtout via map ou des dictionnaires de valeurs optionnelles. Évite-les : utilise flatMap, ou aplatis avec ??.
try? transforme une fonction qui throw en optionnel : succès → la valeur, erreur → nil (l'erreur est avalée).
Pratique quand l'erreur précise ne t'intéresse pas. À éviter quand tu dois savoir pourquoi ça a échoué — là, utilise do/catch. Note : depuis Swift 5.0, try? n'ajoute plus de niveau d'optionnel superflu (pas de Data??).
as? est le cast conditionnel : réussit → valeur typée, échoue → nil. C'est l'équivalent sûr du as! d'ObjC.
9. Pont UIKit : les IBOutlet ! (implicitly unwrapped optionals)
Tu connais ça par cœur :
Le ! après le type, c'est un implicitly unwrapped optional (IUO). Ce n'est pas un type à part : c'est un Optional ordinaire (UILabel? ici) que le compilateur s'autorise à déballer automatiquement à chaque usage, comme s'il n'était pas optionnel.
Pourquoi UIKit l'utilise : l'outlet est nil entre init et le chargement de la vue (viewDidLoad), puis garanti non-nil ensuite. Le ! te laisse l'écrire titleLabel.text = ... sans déballer partout.
Le piège : si tu touches l'outlet avant que la vue soit chargée (ex. depuis l'init, ou un outlet jamais branché dans le storyboard), c'est un crash unexpectedly found nil while unwrapping.
À retenir côté SwiftUI : tu n'as plus du tout ce mécanisme. Pas d'outlets, pas de cycle de vie de vue à attendre. Une @State/let a sa valeur dès la création de la View. Les T! ne survivent en Swift moderne que pour le pont UIKit/storyboard ; ailleurs, considère-les comme une odeur de code.
Points à connaître
if letlimite la valeur déballée à son bloc ;guard letla rend dispo dans tout le reste du scope et impose un early exit. Choisis selon où tu as besoin de la valeur.mapvsflatMap: si ta closure renvoie déjà un optionnel,flatMapévite leT??. Confondre les deux est l'erreur classique.try?avale l'erreur. Si tu as besoin du pourquoi, c'estdo/catch, pastry?.- Un
T!(implicitly unwrapped) crashe pareil qu'un!si tu y accèdes alors qu'il estnil— ce n'est pas « plus sûr », juste plus discret.
Exercice
Refactore ce code truffé de force unwrap en version propre avec guard let et early return. Le but : zéro !, et la fonction sort proprement si une donnée manque.
Objectif : signature func makeURL(from dict: [String: String]) -> URL?, déballage par guard let, et construction de l'URL sans !. Faisable en 15 min.
Question d'entretien
Q : Quelle est la différence entre if let et guard let, et quand utiliser l'un plutôt que l'autre ?
R : Les deux déballent un optionnel de façon sûre. if let rend la valeur disponible uniquement dans le bloc if — pratique pour un traitement local et optionnel. guard let impose de quitter le scope dans son else (return, throw, etc.) et rend la valeur disponible dans tout le reste de la fonction — idéal pour valider les préconditions en début de fonction et garder le « chemin heureux » non imbriqué (early return). Règle : si l'absence doit interrompre la suite, c'est guard ; si c'est juste une branche parmi d'autres, c'est if let.
Q : Pourquoi Int("abc") renvoie un optionnel et pas un crash ?
R : Parce que la conversion peut légitimement échouer à l'exécution (la chaîne n'est pas un nombre). Swift modélise cet échec dans le type (Int?) au lieu d'un crash ou d'une valeur sentinelle comme 0. Le compilateur force alors l'appelant à gérer l'échec.
Q : C'est quoi un implicitly unwrapped optional, et où en croise-t-on en pratique ?
R : Un T! : un optionnel qui se déballe automatiquement à l'usage. On le trouve surtout sur les @IBOutlet UIKit, qui sont nil entre l'init et le chargement de la vue puis garantis non-nil ensuite. Y accéder trop tôt crashe pareil qu'un force unwrap. En Swift moderne hors pont UIKit, on l'évite.
Résumé
Optional<T>est un enum.some/.none; Swift force à gérer l'absence au lieu dunilsilencieux d'ObjC.if let(shorthand Swift 5.7) déballe dans un bloc ;guard letdéballe + early return pour le reste du scope.??donne une valeur par défaut,?.chaîne sans déballer,map/flatMaptransforment la valeur si elle existe (flatMapévite le double optionnel).!(force unwrap) : acceptable seulement quandnil= bug de prog (URL littérale, ressource bundle) ; dangereux sur toute donnée externe.try?→ optionnel en avalant l'erreur ;as?→ cast conditionnel sûr.- Les
@IBOutlet var x: UILabel!sont des implicitly unwrapped optionals : crash si accédés avant le chargement de la vue. Ce mécanisme disparaît en SwiftUI.