Historique de la question :
Avec le développement d'Objective-C et l'émergence de Swift chez Apple, la question de la gestion automatique et sécurisée de la mémoire des applications sans intervention explicite du programmeur avec les commandes retain/release s'est posée. Swift utilise l'Automatic Reference Counting (ARC) qui permet de suivre le nombre de références aux objets de classe et de libérer automatiquement la mémoire lorsque le dernier propriétaire disparaît.
Problème :
Étant donné que Swift est un langage multi-paradigmes, il manipule à la fois des types valeur (struct, enum) et des types référence (class). Pour ces derniers, il est crucial que la mémoire soit libérée à temps, sinon cela peut entraîner une fuite de mémoire. Une situation complexe se présente lors d'un retain cycle (références circulaires), lorsque deux objets se réfèrent mutuellement, empêchant l'ARC de libérer la mémoire.
Solution :
Swift applique l'ARC uniquement aux objets de classe. Lorsque le nombre de références atteint 0, la mémoire est libérée. Pour résoudre le problème des retain cycles, les mots-clés weak/unowned sont utilisés.
Exemple de code :
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) est en cours de désinitialisation") } } class Pet { var owner: Person? init() {} deinit { print("L'animal de compagnie est en cours de désinitialisation") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Les deux objets NE sont PAS libérés, un retain cycle s'est produit
Caractéristiques clés :
Peut-on appliquer ARC aux types valeur ?
Non, ARC ne suit que les références aux objets classes. Les structures et enums sont passés par valeur, et Swift libère leur mémoire automatiquement (généralement à la sortie de la portée).
Que se passe-t-il si l'on n'utilise pas weak/unowned dans les propriétés de deux objets référents mutuellement ?
Un lien circulaire (retain cycle) se produira, empêchant l'ARC de libérer la mémoire pour ces objets. Un tel code entraînera une fuite de mémoire — utilisez toujours weak (ou unowned) pour au moins un côté de cette relation.
À quoi sert unowned, si weak est disponible ?
unowned est nécessaire dans les cas où la référence doit toujours exister pendant la vie de l'autre objet et ne doit jamais devenir nil. weak est une référence nulle, unowned est non nulle, mais sans augmentation du compteur de retenue.
Exemple de code :
class A { var b: B? } class B { unowned var a: A // jamais nil pendant la vie de B }
Deux contrôleurs contiennent des références fortes l'un à l'autre : l'un via une propriété déléguée, l'autre via un tableau de contrôleurs enfants. Le créateur n'utilise pas weak pour le délégué.
Avantages : Les erreurs ne sont pas immédiatement visibles, tout "fonctionne" à première vue.
Inconvénients : Avec l'augmentation de la taille de l'application, la mémoire n'est pas libérée, des fuites apparaissent ; lors de l'utilisation d'une mémoire limitée, des crashs peuvent se produire.
Le délégué et l'observateur d'événements sont configurés comme weak, le tableau de contrôleurs est nettoyé au fur et à mesure que les contrôleurs disparaissent. Tout est soigneusement documenté.
Avantages : Pas de fuites, tous les objets sont libérés à temps, aucune perte de performance.
Inconvénients : Possibilité de perte du délégué de façon inattendue (si une référence forte est oubliée quelque part au-dessus) ; cela nécessite de la vigilance dans l'architecture de l'application.