Gestion des erreurs en Swift : try/catch et bien plus
Dans le développement iOS et macOS moderne, la fiabilité d'une application est l'un des indicateurs clés de qualité. Une gestion correcte des erreurs en Swift permet non seulement d'éviter des plantages inattendus, mais aussi de rendre le code plus lisible et prévisible. Dans cet article, nous aborderons non seulement le mécanisme classique try/catch, mais aussi des approches alternatives, notamment le type Result, l'opérateur defer et des patterns avancés.
Les bases : le protocole Error et le mot-clé throws
En Swift, les erreurs sont représentées par des types qui se conforment au protocole Error. Le plus souvent, on utilise des énumérations (enum) pour cela. Pour qu'une fonction puisse lancer une erreur, le mot-clé throws est utilisé dans sa déclaration.
enum NetworkError: Error { case noInternet case serverError(code: Int) case timeout}
func fetchData(from url: String) throws -> Data { guard url.hasPrefix("https") else { throw NetworkError.serverError(code: 400) } // ... simulation de chargement return Data()}Notez : si une fonction est marquée throws, le code appelant doit gérer l'erreur potentielle à l'aide de try, try? ou try!.
La construction do-catch : l'approche classique
La méthode de gestion la plus courante est le bloc do-catch. Vous placez le code potentiellement dangereux à l'intérieur de do, et dans catch, vous décrivez quoi faire avec chaque erreur spécifique.
do { let data = try fetchData(from: "http://example.com") print("Données reçues : \\(data)")} catch NetworkError.noInternet { print("Pas de connexion Internet")} catch NetworkError.serverError(let code) { print("Erreur serveur avec le code : \\(code)")} catch { print("Erreur inconnue : \\(error.localizedDescription)")}Important : le dernier bloc catch sans motif intercepte toutes les erreurs restantes. C'est une bonne pratique pour ne pas manquer des exceptions inattendues.
try? et try! : des alternatives rapides
Si le type d'erreur ne vous importe pas et que vous voulez seulement savoir si la fonction a réussi, utilisez try?. En cas d'erreur, le résultat sera nil.
if let data = try? fetchData(from: "https://valid.com") { print("Succès : \\(data)")} else { print("Erreur lors du chargement")}L'opérateur try! est une « bombe à retardement ». Il supprime la gestion des erreurs et provoque un plantage de l'application si une erreur survient. Utilisez-le uniquement dans les cas où vous êtes sûr à 100 % qu'il n'y a pas d'erreur (par exemple, lors du chargement d'une ressource intégrée).
let data = try! fetchData(from: "https://trusted-source.com")// Si erreur — l'application planteraLe type Result : une approche fonctionnelle
Depuis Swift 5, le type Result<Success, Failure> a été ajouté à la bibliothèque standard. C'est une closure qui peut contenir soit une valeur de succès, soit une erreur. Cette approche est particulièrement pratique dans le code asynchrone et dans les fonctions de rappel (completion handlers).
func fetchDataResult(from url: String) -> Result<Data, NetworkError> { guard url.hasPrefix("https") else { return .failure(.serverError(code: 400)) } return .success(Data())}
// Utilisationlet result = fetchDataResult(from: "http://bad.com")switch result {case .success(let data): print("Données : \\(data)")case .failure(let error): print("Erreur : \\(error)")}L'avantage de Result est que vous n'oubliez pas de gérer l'erreur, car le compilateur vous y oblige.