defer est une construction de contrôle de flux en Swift qui planifie l'exécution d'un bloc de code au moment de la sortie de la portée actuelle. Le bloc defer s'exécute indépendamment de la façon dont la portée se termine — return, break, throw, fatalError ou achèvement normal. Selon le Swift Language Guide (2025), lorsque plusieurs defers sont présents dans une même portée, ils s'exécutent dans l'ordre inverse de leur déclaration — le dernier déclaré s'exécute en premier (LIFO). Cela rend defer indispensable pour le nettoyage garanti des ressources : fermer les descripteurs de fichiers, libérer les verrous, libérer les pointeurs temporaires sans risque d'omettre le nettoyage lors d'une sortie anticipée.
Points Clés
defer est une construction de contrôle de flux en Swift, introduite dans Swift 2.0 (2015), qui retarde l'exécution de son bloc jusqu'à ce que la portée actuelle se termine. La caractéristique clé : defer garantit que son corps s'exécute indépendamment de la façon dont la portée se termine — avec succès (return), avec une erreur (throw), prématurément (break, continue) ou fatalement (fatalError, precondition).
Syntaxiquement, defer s'écrit comme defer { /* code */ } et peut être placé n'importe où dans la portée. Le compilateur Swift garantit que le code à l'intérieur de defer sera exécuté même si une exception ou un return survient entre la déclaration du defer et la fin de la portée. Cela distingue fondamentalement defer du code normal placé à la fin d'une fonction, qui peut être ignoré lors d'une sortie anticipée.
Selon un article de Chris Lattner (Créateur de Swift, 2015), defer s'est inspiré de constructions similaires dans d'autres langages — defer en Go, finally en Java/Python, scope guard en C++ — mais avec une différence importante : en Swift, defer s'exécute à la fin de la portée, pas immédiatement après un bloc try-catch. Cela offre un comportement plus prévisible pour le nettoyage dans les fonctions avec plusieurs points de sortie.
Utilisez defer pour une gestion symétrique des ressources : ouvrir un fichier → defer { close }, acquérir un verrou → defer { unlock }. Ce modèle garantit que la libération des ressources n'est jamais oubliée en aucune circonstance.
Lorsque plusieurs defers sont déclarés dans la même portée, ils s'exécutent dans l'ordre inverse de leur déclaration (LIFO — Last In, First Out). Cela signifie que le dernier defer déclaré s'exécute en premier, et le premier s'exécute en dernier :
func exampleDeferOrder() {
defer { print("Premier defer") }
defer { print("Deuxième defer") }
defer { print("Troisième defer") }
print("Corps de la fonction")
}
// Sortie :
// Corps de la fonction
// Troisième defer
// Deuxième defer
// Premier defer
L'ordre LIFO est important pour la gestion correcte des ressources imbriquées. Si le fichier A est ouvert en premier, puis le fichier B, ils doivent être libérés dans l'ordre inverse : d'abord B, puis A. Avec defer, cela se fait automatiquement — déclarez un defer juste après l'ouverture de chaque ressource, et l'ordre de nettoyage sera correct quel que soit le nombre de points de sortie de la fonction.
Selon Swift by Sundell (2024), cette fonctionnalité rend defer idéal pour les verrous imbriqués et les transactions : acquérir un verrou → defer { unlock } → acquérir le suivant → defer { unlock }. LIFO garantit que les verrous sont libérés dans l'ordre inverse de leur acquisition, empêchant les interblocages.
Le cas d'utilisation principal de defer est le nettoyage garanti des ressources. Considérons le travail avec le système de fichiers. Ouvrir un fichier via FileHandle nécessite une fermeture explicite — defer garantit que close sera appelé dans tout scénario :
func readFile(path: String) throws -> String {
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer { try? handle.close() }
let data = try handle.readToEnd()
guard let data else { throw FileError.empty() }
return String(data: data, encoding: .utf8) ?? ""
// handle.close() sera appelé même en cas de throw ou return
}
Un autre scénario typique est les animations d'interface avec un indicateur de chargement. Avant de lancer un chargement, définissez l'indicateur isLoading = true, et defer le remet à false à la sortie de la fonction, indépendamment du succès ou de l'échec de la requête. Cela empêche l'indicateur de rester true en raison d'une erreur non traitée, ce qui bloquerait l'interface pour toujours.
Selon le Bitbucket Engineering Blog (2024), defer est également utilisé pour le profilage : enregistrez le temps au début d'une fonction, et dans defer — calculez et affichez la différence. Cela donne des mesures de performance précises pour tous les chemins d'exécution, y compris les chemins erronés.
defer fonctionne efficacement avec les fonctions throws. Lorsqu'une fonction peut lancer une erreur à n'importe quel stade, defer garantit le nettoyage sans dupliquer le code dans chaque bloc catch ou sortie anticipée avec guard :
func processTransaction() throws {
let db = try openDatabase()
defer { closeDatabase(db) }
let user = try fetchUser(from: db)
defer { logAudit(user) }
let result = try performPayment(user)
sendNotification(result)
// closeDatabase(db) et logAudit(user) seront appelés
// sur tout throw ou return
}
Important : defer s'exécute avant le transfert du contrôle hors du bloc catch, mais après la survenue de l'erreur. Si une erreur est lancée à l'intérieur de defer, Swift n'autorise pas l'utilisation de try directement dans defer — vous avez besoin de try? ou try!. Selon la documentation Apple, Swift ne permet pas à une erreur de s'échapper de defer, car cela violerait la garantie d'exécution du bloc.
Placez defer immédiatement après l'acquisition de la ressource. Cela suit le principe de proximité : le lecteur voit l'acquisition et la libération côte à côte, ce qui améliore la fiabilité du code et simplifie la revue de code.
defer s'exécute à la sortie de la portée dans laquelle il est déclaré. Si defer est déclaré à l'intérieur d'un bloc do, il s'exécute à la sortie de ce bloc, pas de la fonction externe. S'il est à l'intérieur d'une boucle for — à chaque itération :
func scopeExample() {
print("start")
do {
defer { print("defer du bloc do") }
print("inside do")
}
// "defer du bloc do" s'affiche ici
print("after do")
}
// Sortie : start, inside do, defer du bloc do, after do
for i in 1...3 {
defer { print("end iteration \(i)") }
print("iteration \(i)")
}
// Sortie : iteration 1, end iteration 1, iteration 2, end iteration 2, ...
Les variables capturées par defer sont lues au moment de la sortie de la portée, pas au moment de la déclaration du defer. Si une variable change entre la déclaration du defer et la fin de la portée, defer verra la dernière valeur. C'est une distinction importante par rapport aux closures, où la capture se produit au moment de la création. Soyez prudent : les modifications d'une variable après la déclaration de defer affecteront son exécution.
La première erreur — supposer un ordre d'exécution autre que LIFO. Si l'ordre de nettoyage est important et que les defers sont déclarés dans le mauvais ordre, les ressources peuvent être libérées avec des violations de dépendances. Solution : déclarez defer immédiatement après avoir capturé chaque ressource. Deuxième ressource ouverte → defer { close second } avant que la première ne soit fermée.
La deuxième erreur — utiliser defer pour une logique non liée au nettoyage. defer est conçu pour un nettoyage garanti, pas pour le contrôle de flux principal. Si le code dans defer affecte la valeur de retour, c'est presque toujours une erreur. defer ne peut pas modifier la valeur de retour d'une fonction (contrairement à Java finally, où return dans finally écrase le return original).
La troisième erreur — lancer une erreur depuis defer. Swift interdit try à l'intérieur de defer si l'erreur pourrait se propager vers l'extérieur. Utilisez try? ou try! pour les opérations qui pourraient lancer une erreur, ou enveloppez-les dans une fonction séparée sans throws. Selon O’Reilly “Swift in Depth” (2025), une bonne pratique est de rendre les fonctions de nettoyage non lançantes (non-throwing) ou de gérer les erreurs à l'intérieur de defer.
Questions Fréquentes
defer est une construction Swift qui retarde l'exécution d'un bloc jusqu'à la sortie de la portée actuelle. Le bloc s'exécute toujours — sur return, throw, break ou achèvement normal. Il est utilisé pour le nettoyage garanti des ressources : fermer des fichiers, libérer des verrous.
Dans l'ordre inverse de déclaration (LIFO) — le dernier defer déclaré s'exécute en premier. Cela garantit un nettoyage correct des ressources imbriquées : si la ressource B est ouverte après A, elle sera fermée avant A, empêchant les dépendances sur des ressources déjà libérées.
Pas directement — Swift empêche la propagation d'erreur depuis defer. Utilisez try? ou try! pour les opérations qui pourraient lancer une erreur. La meilleure pratique est de rendre les fonctions de nettoyage non lançantes ou de gérer les erreurs à l'intérieur de defer sans les propager vers l'extérieur.
defer est lié à une portée et s'exécute à toute sortie, y compris return, throw et break. finally (dans d'autres langages) est lié à try-catch et ne s'exécute qu'en présence de try. Swift n'a pas de finally — defer couvre complètement ce scénario et fonctionne pour toute portée, pas seulement pour la gestion des erreurs.
Oui, defer lit les variables au moment de la sortie de la portée, pas au moment de la déclaration. Si une variable change après la déclaration de defer, le bloc defer verra la dernière valeur. Cela diffère des closures normales, où la capture est fixée au moment de la création.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi