Pourquoi Swift n'utilise-t-il pas un ramasse-miettes traditionnel ?
\nSi vous venez dans le monde Swift depuis Python ou Java, vous avez sûrement été surpris que ce langage n'ait pas de ramasse-miettes (Garbage Collector) classique. À la place, Apple a intégré le mécanisme Automatic Reference Counting (ARC) — le comptage automatique des références. Et ce n'est pas un simple caprice : sur les appareils mobiles, chaque cycle processeur et chaque octet de mémoire comptent. Un GC traditionnel « gèle » l'application pendant la collecte, ce qui serait catastrophique pour iOS avec son exigence de fluidité des animations (60 FPS).
\nDans cet article, nous allons voir comment fonctionne réellement la gestion de la mémoire en Swift, où se cachent les pièges des références cycliques, et comment écrire un code qui ne « fuit » pas.
\n\nARC : comment ça fonctionne sous le capot ?
\nL'ARC n'est pas un ramasse-miettes au sens habituel. Il fonctionne à l'étape de compilation : le compilateur insère les instructions retain et release aux endroits appropriés. Mais il y a un détail : l'ARC ne sait pas gérer les références cycliques (retain cycles). Si deux objets se tiennent mutuellement par des références fortes, leur compteur de références ne tombera jamais à zéro — la mémoire fuit.
Exemple : une classe simple et le comptage des références
\nclass Person {\n let name: String\n init(name: String) {\n self.name = name\n print("\\(name) est apparu en mémoire")\n }\n deinit {\n print("\\(name) a disparu de la mémoire")\n }\n}\n\nvar person1: Person? = Person(name: "Alice") // retain count = 1\nvar person2 = person1 // retain count = 2\nperson1 = nil // retain count = 1\nperson2 = nil // retain count = 0 → deinit\n\nDans la console, vous verrez : « Alice est apparu en mémoire » et un peu plus tard — « Alice a disparu de la mémoire ». Tout est correct.
\n\nRéférences cycliques : le principal ennemi de la mémoire
\nImaginez : vous avez un User et un Account. L'utilisateur a un compte, et le compte connaît son propriétaire. Si les deux propriétés sont des références fortes, on obtient un cercle vicieux :
class User {\n let name: String\n var account: Account?\n init(name: String) { self.name = name }\n deinit { print("User \\(name) supprimé") }\n}\n\nclass Account {\n let number: String\n var owner: User?\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) supprimé") }\n}\n\nvar user: User? = User(name: "Boris")\nvar account: Account? = Account(number: "12345")\nuser?.account = account\naccount?.owner = user\n\nuser = nil // User n'est pas supprimé ! account?.owner tient toujours la référence\naccount = nil // Account n'est pas supprimé ! user?.account tient la référence\n// Fuite de mémoire !\n\nDans la console, aucun message deinit n'apparaîtra. Les objets restent en mémoire pour toujours. C'est un retain cycle — un problème classique de gestion de la mémoire en Swift.
Comment le traiter ? Weak et unowned
\nLa solution est de rendre l'une des références faible (weak) ou non possédée (unowned). La différence :
- \n
- weak — toujours optionnel, devient automatiquement
nillorsque l'objet est supprimé. Sûr. \n - unowned — non optionnel, suppose que l'objet vit plus longtemps. Si on accède à un objet supprimé — crash. \n
class Account {\n let number: String\n weak var owner: User? // ← référence faible\n init(number: String) { self.number = number }\n deinit { print("Account \\(number) supprimé") }\n}\n// Maintenant, user = nil libérera User, et account?.owner deviendra nil\n\n\nRamasse-miettes vs ARC : lequel est plus rapide ?
\nSoyons honnêtes : l'ARC n'est pas un « ramasse-miettes » au sens classique. Un vrai GC (comme en Java, Go ou Python) fonctionne à l'exécution : il scanne périodiquement le tas, trouve les objets inaccessibles et les libère. Cela nécessite des pauses (stop-the-world).
\nL'ARC, quant à lui, est un mécanisme stat