ProgrammationDéveloppeur iOS Middle/Senior

Quelle est la nature de l'opérateur defer en Swift et quels sont les principaux scénarios de son utilisation, les caractéristiques du travail avec des closures et la gestion des ressources ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Historique de la question

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.

Problème

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.

Solution

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 :

  • S'exécute lors de toute sortie de la portée (return, throw, break),
  • Plusieurs defer peuvent être déclarés consécutivement — ils s'exécutent dans l'ordre inverse,
  • Très utile pour la gestion des ressources, les transactions d'erreurs, les verrouillages/déverrouillages, la réinitialisation des états temporaires.

Questions piégées.

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.

Erreurs typiques et anti-patterns

  • Utiliser defer pour des opérations asynchrones (le dernier defer libère, mais le code asynchrone n’est pas encore terminé),
  • Capturer implicitement des variables externes mutables (condition de compétition en multi-threading),
  • Déclarer trop de defer consécutifs — cela nuit à la lisibilité et au débogage.

Exemple de la vie

Cas négatif

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 :

  • Logique simple et linéaire

Inconvénients :

  • Facile d'oublier de libérer la ressource à chaque sortie
  • Fuite de mémoire/fuite de ressources

Cas positif

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 :

  • Haute sécurité de la ressource
  • Réduction du nombre d'erreurs

Inconvénients :

  • Imbrication supplémentaire de la portée avec un grand nombre de defer