Historique de la question :
Les closures, en tant que concept, proviennent des langages fonctionnels et permettent de passer des blocs de code comme des valeurs. En Swift, les closures (analogues aux lambdas d'autres langages) existent depuis le début. Cela permet de réaliser élégamment des appels asynchrones, de déléguer des actions, de trier des collections, de filtrer, etc.
Problème :
Les closures capturent des variables du contexte environnant. Si l'une de ces variables est une référence à un objet de classe, un cycle de rétention peut se produire, surtout si la closure est stockée en tant que propriété de cette classe. Il est également important de comprendre la différence entre les closures échappantes et non échappantes, et de savoir comment fonctionne la capture des valeurs.
Solution :
Swift implémente les closures en tant qu'objets autonomes, elles peuvent capturer le contexte, et le détenteur de la closure doit être prudent avec l'architecture.
Exemple de code :
class Performer { var onComplete: (() -> Void)? func doWork() { onComplete = { print("Travail terminé !") } } } // Capture de self dans la closure class Test { var value = 0 func start() { DispatchQueue.global().async { [weak self] in self?.value = 1 } } }
Caractéristiques clés :
Si la closure ne capture pas self explicitement, un cycle de rétention peut-il se produire ?
Oui, si la closure est stockée comme une propriété forte de la classe et accède à self à l'intérieur, un cycle de rétention se crée, même si vous n'écrivez pas explicitement self. Utilisez [weak self]/[unowned self] ou faites de la closure une locale, si possible.
Quelle est la différence entre une closure échappante et non échappante ?
Une closure échappante peut être appelée après la fin de la fonction et est généralement utilisée pour des opérations asynchrones. Une closure non échappante est exécutée avant la fin de l'exécution de la fonction. L'ordre de capture de self est différent pour la capture échappante.
Exemple de code :
func asyncWork(completion: @escaping () -> Void) { DispatchQueue.global().async { completion() } }
La closure peut-elle changer les valeurs des variables capturées ?
Oui, si la closure a capturé une variable déclarée comme var, elle peut changer sa valeur (pour les types valeur). Pour une classe, cela sera une référence et on peut toujours modifier les propriétés.
Exemple de code :
var value = 1 let closure = { value += 1 } closure() print(value) // 2
ViewController conserve une closure comme propriété (par exemple, completionHandler) et accède directement à self à l'intérieur. Cela crée un cycle de rétention : ViewController => closure => ViewController. La désactivation de l'écran ne libère pas la mémoire.
Avantages : Le code est concis et semble court.
Inconvénients : Fuites de mémoire, ViewController "s'accroche" à la mémoire, bugs potentiels.
Utilisation de [weak self] ou [unowned self] à l'intérieur de la closure, ou la closure est stockée pas plus longtemps que le cycle de vie de l'objet. Revoir ces endroits lors de la revue de code.
Avantages : Libération correcte des ressources, absence de fuites imprévisibles.
Inconvénients : [weak self] nécessite de la prudence lors de la désacralisation, des crashs implicites peuvent se produire à cause d'une mauvaise utilisation.