L'opérateur defer a été introduit dans Swift pour assurer un nettoyage sûr et garanti des ressources ou l'exécution de code lors de la sortie d'une portée, analogue à finally/using/RAII dans d'autres langages.
L'initialisation en plusieurs étapes, le travail avec des fichiers, les scénarios de sortie croisés de la fonction nécessitent un nettoyage garanti des ressources ou l'exécution de logique (fermer un fichier, débloquer un mutex, retourner un objet dans un pool, réinitialiser un état temporaire). Avant l'apparition de defer, tout devait être fait manuellement à chaque retour.
defer garantit que son code s'exécute lors de la sortie de la portée actuelle, peu importe si la sortie est normale ou via un throw. Il est possible de déclarer plusieurs defer ; ils s'exécuteront dans l'ordre inverse de leur déclaration.
Exemple de libération de ressource:
func processFile(path: String) throws { let file = try openFile(path) defer { file.close() } // S'exécutera même en cas d'erreurs // ... traitement avec le fichier ... }
Caractéristiques clés :
Le defer peut-il capturer des variables par référence et comment cela affecte-t-il le comportement des closures ?
Oui, defer capture toutes les variables utilisées au moment de l'appel, selon les règles de capture des closures (copie pour les types value, référence pour les types reference). Une erreur se produira si une variable externe mutable de type value est capturée — elle peut changer au moment de l'exécution de defer.
Comment fonctionne un defer imbriqué à l'intérieur des boucles et des fonctions ?
Si defer est déclaré à l'intérieur d'une boucle, il s'exécute à chaque fin d'itération de la portée :
for _ in 1...3 { defer { print("Fin de l'itération") } print("À l'intérieur") }
Affichera :
À l'intérieur
Fin de l'itération
À l'intérieur
Fin de l'itération
À l'intérieur
Fin de l'itération
Le defer peut-il ne pas s'exécuter ?
L'exécution du code defer est garantie seulement si la portée est effectivement quittée. Mais si le processus se termine de manière anormale (fatalError, crash) ou si exit est appelé, defer ne s'exécute pas. De plus, defer ne fonctionne pas au niveau des méthodes d'objets — seulement dans la portée de la fonction/bloc.
Dans le code après l'ouverture d'un fichier et le travail avec lui, le retour se faisait par différents return/throw, et nous avons oublié de fermer le fichier à un endroit. En conséquence, le gestionnaire de fichiers ouvert a fui dans le système.
Avantages :
Inconvénients :
Utilisation de defer pour garantir le déverrouillage d'un mutex, la libération d'un descripteur de fichier et la réinitialisation d'un indicateur de progression :
func criticalSection() { lock() defer { unlock() } // ... travail ... }
Avantages :
Inconvénients :