Gestion des erreurs en Swift : try/catch et au-delà
Le développement pour iOS et macOS nécessite une gestion fiable des erreurs. Swift fournit un mécanisme de gestion des défaillances puissant et élégant, basé sur les mots-clés throws, try, catch et do. Dans cet article, nous allons voir comment gérer correctement les erreurs, écrire du code lisible et éviter les pièges courants.
1. Les bases : le protocole Error et le mot-clé throws
Toute erreur en Swift doit être conforme au protocole Error. On utilise généralement des énumérations (enum) pour cela :
enum NetworkError: Error { case badURL case noData case decodingFailed(String)}Une fonction qui peut lever une erreur est marquée par le mot-clé throws :
func fetchUser(from urlString: String) throws -> User { guard let url = URL(string: urlString) else { throw NetworkError.badURL } // ... logique de la requête}Désormais, lors de l'appel d'une telle fonction, le compilateur vous obligera à gérer l'erreur potentielle.
2. La structure do-catch : interception des erreurs
La manière la plus courante de gérer les erreurs est le bloc do-catch. Vous placez le code potentiellement dangereux dans do, et dans catch vous gérez la défaillance :
do { let user = try fetchUser(from: "https://api.example.com/user") print("Utilisateur chargé : \\(user.name)")} catch NetworkError.badURL { print("URL invalide")} catch NetworkError.noData { print("Le serveur n'a pas retourné de données")} catch NetworkError.decodingFailed(let message) { print("Erreur de décodage : \\(message)")} catch { print("Erreur inconnue : \\(error.localizedDescription)")}Important : le dernier bloc catch sans type spécifique attrape toutes les erreurs restantes — il est conseillé de toujours l'ajouter.
2.1. Filtrage par motif dans catch
Vous pouvez utiliser des conditions plus complexes :
do { let result = try riskyOperation()} catch let error as NetworkError where error == .badURL { print("Problème avec l'URL")} catch { print(error)}3. Alternatives : try? et try!
Une gestion complète n'est pas toujours nécessaire. Swift propose deux approches simplifiées :
3.1. try? — valeur optionnelle
Si l'erreur n'est pas critique, vous pouvez convertir le résultat en optionnel. En cas d'échec, la fonction retournera nil :
let user = try? fetchUser(from: "https://example.com/user")// user est de type User? — nil en cas d'erreurIdéal pour les situations où l'erreur peut simplement être ignorée (par exemple, le chargement d'un avatar).
3.2. try! — exécution forcée
Utilisez try! uniquement lorsque vous êtes absolument certain qu'il n'y aura pas d'erreur. Sinon, l'application plantera :
let user = try! fetchUser(from: "https://valid-url.com/user")// Si l'URL est invalide — l'application planteraAppliquez try! dans les tests ou dans des situations où une défaillance est impossible selon la logique du programme.
4. Techniques avancées : rethrows et deferred
4.1. rethrows pour les fonctions d'ordre supérieur
Si votre fonction accepte une closure qui peut lever une erreur, utilisez rethrows. Cela permet de ne pas marquer votre propre fonction comme throws si la closure ne lève pas d'erreur :
func process(_ closure: () throws -> Void) rethrows { try closure()}
// Appel sans closure throws — ne nécessite pas do-catchprocess { print("Sans erreurs") }
// Appel avec closure throws — nécessite une gestiontry process { throw NetworkError.noData }4.2. deferred : nettoyage garanti
Le bloc defer s'exécute à la fin de toute fonction (succès ou erreur). Parfait pour fermer des fichiers ou libérer des ressources.