Fiche 00.03 — Enum, valeurs associées et pattern matching
Objectif
Maîtriser les enums Swift au-delà du simple NSInteger UIKit : rawValue, CaseIterable, et surtout les valeurs associées qui modélisent les états d'écran et les routes de navigation. Le pattern matching (switch, if case let, where) est l'outil qui rend ce code exhaustif et sûr.
1. Enum simple, rawValue, CaseIterable
En UIKit tu utilisais souvent des enums C-style (UITableViewCellStyle) ou des constantes Int. En Swift, un enum est un vrai type, plus riche.
Le rawValue donne une valeur brute optionnelle à chaque cas. Pratique pour mapper un backend.
Int raw values : auto-incrémentées si tu n'en mets pas.
CaseIterable te donne allCases, idéal pour remplir un Picker ou itérer (l'équivalent de lister tes constantes à la main en UIKit).
Piège : un enum avec valeurs associées ne peut pas avoir de
rawValue. Les deux sont mutuellement exclusifs.
2. Valeurs associées : le vrai pouvoir
Une valeur associée = de la donnée attachée à un cas. C'est ça qui change tout par rapport à un enum C.
Chaque cas porte exactement la donnée qui a du sens dans cet état : loaded a un Profile, error a un message, loading n'a rien. Impossible d'avoir un Profile ET une erreur en même temps — l'état est cohérent par construction.
C'est l'outil idéal pour modéliser les états d'écran (loading / loaded / empty / error) que tu verras partout en SwiftUI, et les routes de navigation :
En UIKit tu poussais un UIViewController concret (pushViewController(_:)). Ici tu pousses une valeur qui décrit la destination, et la View décide quel écran construire. Plus testable, plus découplé.
3. switch exhaustif et extraction
Le switch sur un enum doit être exhaustif : le compilateur t'oblige à traiter tous les cas (ou un default). C'est un filet de sécurité énorme — ajoute un cas à l'enum, et tous les switch non exhaustifs ne compilent plus. Tu ne peux pas oublier un état.
Pour lire la valeur associée, tu la lies avec let (ou var) :
Raccourci quand toutes les valeurs sont des let : tu factorises le mot-clé.
4. where et binding multiple
where ajoute une condition au cas. Pas idéal :
Préférable — la condition est dans le case, le code reste plat :
Tu peux aussi regrouper plusieurs cas qui partagent le même traitement :
Et matcher sur un tuple de deux enums (transition d'état, par ex.) :
5. if case let / guard case let
Quand tu n'as besoin que d'un seul cas, le switch est trop lourd. if case let extrait une valeur sans exhaustivité.
guard case let pour le early-exit (réflexe que tu avais déjà avec guard let sur les optionnels) :
Comparaison directe d'un cas sans valeur associée : pas de == automatique, utilise if case.
Astuce : si l'enum est
Equatable, tu peux écrirestate == .loading. Mais dès qu'il y a une valeur associée à comparer,if casereste le bon outil.
6. Méthodes et propriétés calculées sur un enum
Un enum peut porter du comportement. Plutôt que d'éparpiller des switch dans tes Views, centralise la logique sur l'enum lui-même.
Note : depuis Swift 5.9, un switch qui n'a qu'une expression par cas retourne sa valeur sans return (comme ci-dessus). Idem dans un body SwiftUI.
7. Indirect enum (bref)
Un enum ne peut pas se contenir lui-même… sauf avec indirect, qui ajoute une indirection mémoire. Utile pour des structures récursives (arbre, expression).
Rare en dev app classique, mais ça tombe parfois en entretien. indirect peut aussi être posé sur un seul cas plutôt que tout l'enum.
8. Enum d'état > plusieurs booléens (le refactor clé)
C'est LE point qui te rend crédible. Le pattern "trois propriétés indépendantes" est partout dans le code legacy.
Pas idéal — états impossibles représentables :
Le problème : isLoading == true et profile != nil et errorMessage != nil est un état que rien n'interdit. La View doit deviner la priorité (if isLoading { } else if let error { } else if let profile { }), et tu oublieras un cas un jour. 3 booléens/optionnels = 8 combinaisons, dont 5 illégales.
Préférable — un seul enum, états illégaux impossibles :
La View devient un switch exhaustif, lisible, sans else-if imbriqués :
"Make impossible states impossible" : c'est la phrase à retenir et à ressortir en entretien.
Points à connaître
- Un enum avec valeurs associées n'a pas de
rawValueni de conformitéEquatable/Hashableautomatique si les valeurs portées ne sont pas elles-mêmes conformes — ajoute: Equatableexplicitement si besoin. if case .loading = state(un seul=) n'est PAS une comparaison==: c'est un pattern match. Erreur classique : écrirestate == .loadingsur un enum nonEquatable→ ça ne compile pas.- Oublier un cas dans un
switchne compile pas — c'est voulu. Évitedefaultsur tes enums d'état : tu perds le filet de sécurité quand tu ajoutes un cas plus tard. - Pour la navigation
NavigationStack, ton enum de route doit êtreHashable. Les valeurs associées doivent donc l'être aussi.
Exercice
Pars de ce ViewModel "à l'ancienne" et refactore-le.
- Crée un
enum ViewStateavecloading,loaded([String]),empty,error(String). - Remplace les 3 propriétés par une seule
var state: ViewState. - Écris la
bodyd'une View quiswitchsurstate(un cas chacun,emptyaffiche unContentUnavailableView).
Objectif : zéro if/else if imbriqué dans la View. Faisable en 15 min.
Question d'entretien
Q : À quoi servent les valeurs associées d'un enum ? Donne un cas réel.
R : Elles attachent de la donnée spécifique à chaque cas, ce qui permet de modéliser un état où seules certaines données existent. Cas réel : un ViewState (loading / loaded(Data) / error(String)) — l'état loaded porte la donnée, error porte le message, et il est impossible d'avoir les deux à la fois. Ça remplace plusieurs booléens/optionnels indépendants et élimine les états illégaux (« make impossible states impossible »).
Q : Différence entre if case let et un switch ?
R : switch est exhaustif (le compilateur exige tous les cas) — idéal quand on traite tous les états. if case let extrait un seul cas sans exhaustivité — idéal quand on ne s'intéresse qu'à une situation, en early-exit avec guard case let.
Q : Pourquoi préférer un enum d'état à isLoading + data? + error? ?
R : Trois propriétés indépendantes autorisent des combinaisons incohérentes (chargement ET erreur ET data en même temps). Un enum rend ces états impossibles, force la View à tout gérer via un switch exhaustif, et casse la compilation si on ajoute un cas sans l'afficher — donc moins de bugs.
Résumé
rawValuemappe un enum à une valeur brute (String/Int) ;CaseIterabledonneallCases.- Les valeurs associées attachent de la donnée à un cas : l'outil pour les états d'écran et les routes de navigation.
- Un enum avec valeurs associées n'a pas de
rawValue. switchexhaustif +case .x(let y)extrait la donnée ;wherefiltre ; on peut grouper et matcher des tuples.if case let/guard case letpour un seul cas sans exhaustivité.- Un enum porte méthodes et propriétés calculées : centralise la logique d'état.
indirect enumpour les structures récursives (rare, mais bon à connaître).- Un
enum ViewStatebat plusieurs booléens : il rend les états illégaux impossibles. C'est le réflexe pro à montrer.