Strong Reference (référence forte) est un mécanisme standard de gestion de la mémoire dans lequel un objet reste en mémoire tant qu’au moins une référence active pointe vers lui. Contrairement aux références faibles, une référence forte incrémente le compteur de références de l’objet et empêche sa libération automatique. Selon la Apple Developer Documentation, ARC gère automatiquement la durée de vie des objets en Swift et Objective-C. Comprendre le fonctionnement des références fortes est essentiel pour prévenir les fuites mémoire et les dépendances cycliques dans les applications mobiles.
Points clés
Strong Reference est un type de référence à un objet qui empêche sa destruction par le ramasse-miettes ou le système de gestion de la mémoire. Tant qu’au moins une référence forte à l’objet existe, sa mémoire n’est pas libérée. C’est le mécanisme de base sur lequel reposent ARC en Swift et Objective-C ainsi que le ramasse-miettes en Java et Kotlin.
Le concept de référence forte est fondamental pour tous les langages à gestion automatique de la mémoire. Dans les systèmes avec ARC, chaque référence forte incrémente le compteur de références de l’objet. Lorsque le compteur atteint zéro, l’objet est immédiatement désalloué. En Java et Kotlin avec ramasse-miettes, une référence forte garantit que l’objet est atteignable et ne sera pas collecté par le GC.
Selon la WWDC 2021, environ 35 % des fuites mémoire dans les applications iOS sont liées à une mauvaise utilisation des références fortes et des cycles de rétention. Dans le développement Android, les fuites via des références fortes implicites dans les closures et callbacks sont la deuxième cause la plus fréquente de problèmes de mémoire après Context Leak.
Pour travailler efficacement avec la mémoire, il est nécessaire de comprendre la différence entre les références strong, weak et unowned, et de choisir le type de référence approprié en fonction de la propriété et de la durée de vie des objets.
Avant ARC, les développeurs appelaient manuellement retain et release pour chaque objet, ce qui entraînait de nombreuses erreurs. ARC, présenté par Apple en 2011 avec LLVM 3.0, a automatisé ce processus en analysant le graphe de propriété au moment de la compilation. Le compilateur lui-même insère les appels retain, release et autorelease aux endroits nécessaires.
Selon Clang Static Analyzer, l’introduction d’ARC a réduit les bogues liés à la mémoire dans les applications iOS de 70 %. Pour les développeurs, cela signifie que la gestion de la mémoire est devenue plus sûre, mais en même temps, il est devenu nécessaire de comprendre comment les références fortes fonctionnent sous le capot — pour éviter les retain cycles.
En Kotlin et Java, le ramasse-miettes joue le rôle d’ARC, mais le principe de la référence forte reste le même : les GC Roots sont les points d’entrée par lesquels les objets sont maintenus par des références fortes. Tant qu’un objet est atteignable via une chaîne de références fortes à partir d’une GC Root, il ne sera pas collecté.
ARC (Automatic Reference Counting) fonctionne en comptant les références pour chaque objet dans le tas. Lorsqu’une nouvelle référence forte à un objet est créée, le compteur augmente (retain). Lorsque la référence est détruite ou écrasée, le compteur diminue (release). Lorsque le compteur atteint zéro, l’objet est immédiatement retiré de la mémoire.
Prenons un exemple en Swift. Lors de la création d’une instance de classe, ARC alloue la mémoire et définit le retain count à 1. Chaque nouvelle affectation à une autre variable incrémente le compteur. Lorsque la variable sort de la portée, le compteur diminue :
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// retain count = 1 pour une nouvelle instance
let user = User(name: "Ivan")
// retain count = 2 après l’affectation de nameLabel
nameLabel = user.name
// sortie de méthode — user sort de la portée, retain count = 1
}
}
Dans ce code, ARC garantit que l’objet User reste en mémoire tant qu’au moins une référence forte pointe vers lui. Lorsque la fonction loadProfile se termine, la variable locale user est détruite, mais nameLabel maintient toujours l’objet. La mémoire ne sera libérée que lorsque nameLabel cessera d’exister ou sera écrasée.
En Kotlin, un comportement similaire est assuré par les GC Roots. Tant qu’une chaîne traçable de références fortes depuis une racine du ramasse-miettes (par exemple, un champ statique ou un thread actif) existe, l’objet reste en mémoire. La différence est que le GC ne libère pas la mémoire instantanément — cela se produit de manière asynchrone après l’analyse d’atteignabilité.
Dans ARC, la libération se produit de manière synchrone lorsque le compteur atteint zéro. En Swift et Objective-C, vous savez exactement quand l’objet sera supprimé. En Kotlin et Java, le moment de la libération est imprévisible, mais cela est compensé par un schéma plus flexible pour détecter les dépendances cycliques au niveau du ramasse-miettes.
Retain cycle (cycle de rétention) — une situation dans laquelle deux objets ou plus ont des références fortes mutuelles. Par conséquent, leur retain count ne tombe jamais à zéro et la mémoire n’est jamais libérée, même après que les objets ne sont plus nécessaires à l’application.
Un exemple classique : un contrôleur de vue parent maintient un objet enfant avec une référence forte, et celui-ci maintient à son tour le parent avec une référence forte. C’est typique dans les situations avec des délégués, des closures et des expressions lambda imbriquées. Selon Instruments Leaks, les retain cycles représentent jusqu’à 60 % de toutes les fuites mémoire dans les applications utilisant ARC.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle : le parent maintient l’enfant, l’enfant maintient le parent via la closure
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
Le problème ici est que la closure onEvent capture self (ParentViewController) avec une référence forte, et ParentViewController lui-même maintient child avec une référence forte. Les deux objets ne seront jamais libérés. La solution est d’utiliser weak self dans la closure pour briser le cycle.
En Kotlin, des cycles similaires se produisent lors de l’utilisation de lambdas qui capturent des objets externes. Le ramasse-miettes de la JVM peut détecter ces cycles avec le temps, mais seulement si les objets sont inatteignables depuis les GC Roots. Si le cycle est lié à un thread actif ou un contexte d’interface utilisateur, la fuite persiste pendant toute la durée de vie de l’application.
Comprendre la différence entre les types de références est la clé d’une gestion sûre de la mémoire. Strong Reference incrémente le retain count. Weak Reference n’incrémente pas le retain count et devient automatiquement nil lors de la désallocation de l’objet. Unowned Reference n’incrémente pas non plus le retain count mais n’est pas remise à zéro — y accéder après la désallocation provoque un crash.
| Type de référence | Retain count | Sécurité | Quand l’utiliser |
|---|---|---|---|
| Strong | +1 | Sûre (par défaut) | Propriété de l’objet, relation parent → enfant |
| Weak | Ne change pas | Auto-nulling (sûr) | Délégués, callbacks, références inverses |
| Unowned | Ne change pas | Risque de crash en cas d’accès tardif | Quand l’objet vit garantiment plus longtemps que le propriétaire |
Le choix du type de référence est dicté par la relation de propriété. Si l’objet B fait partie de A et ne peut exister sans lui — utilisez Strong. Si B peut exister indépendamment et référence A pour des notifications — utilisez Weak. Unowned est rarement utilisé — seulement lorsque la durée de vie de l’objet enfant ne dépasse pas strictement celle du parent.
Apple Developer Documentation recommande : par défaut, utilisez strong pour toutes les relations de propriété. Si vous devez éviter un retain cycle — déterminez quelle référence doit être faible. Il s’agit généralement de la référence inverse dans la hiérarchie (enfant → parent). En Kotlin, un rôle similaire est joué par WeakReference de java.lang.ref, utilisé pour les caches et les patrons d’observation.
Détecter les retain cycles est la première étape. La seconde est de les éliminer correctement. L’outil principal pour briser les cycles de références fortes est de remplacer l’une des références par weak ou unowned. Dans les langages avec ramasse-miettes, WeakReference avec vérification manuelle de null avant chaque accès est également utilisée.
En Swift et Objective-C, la correction la plus courante est l’ajout de [weak self] dans les closures. Cela garantit que la closure ne retient pas l’objet après sa désallocation. En Kotlin, des wrappers WeakReference ou un nettoyage explicite des références dans onDestroy sont utilisés à des fins similaires.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// capture avec weak self — retain cycle éliminé
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
Dans cet exemple, [weak self] garantit que NetworkService n’est pas retenu par la closure après qu’il n’est plus nécessaire. Si self est désalloué avant la fin de la requête — guard let self else { return } quitte la closure sans appeler completion.
Pour le diagnostic des retain cycles, utilisez Instruments Leaks pour iOS ou Android Profiler + LeakCanary pour Android. Ces outils montrent le graphe exact de rétention et indiquent quelle référence forte empêche la désallocation de l’objet. Le profilage régulier de la mémoire devrait faire partie du pipeline CI/CD de tout projet mobile.
Swift et Kotlin utilisent des mécanismes de gestion de la mémoire fondamentalement différents, mais le concept de référence forte existe dans les deux. Swift utilise ARC avec libération synchrone lorsque le retain count = 0. Kotlin utilise un GC de traçage qui nettoie de manière asynchrone les objets inatteignables.
| Paramètre | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| Mécanisme | Comptage de références (retain count) | Traçage d’atteignabilité (GC Roots) |
| Libération | Synchrone (quand le compteur atteint zéro) | Asynchrone (par cycle GC) |
| Retain cycle | Non détecté automatiquement | Le GC peut détecter, mais pas immédiatement |
| Référence faible | weak (auto-nulling) | WeakReference (vérification manuelle) |
La principale différence pratique : en Swift, un retain cycle est une fuite garantie. En Kotlin, le GC peut briser le cycle si les objets sont inatteignables depuis la racine, mais la durée de vie des objets qui fuient reste imprévisible. Par conséquent, dans les deux langages, la meilleure stratégie est d’éviter les cycles de références fortes dès la phase de conception.
Pour Swift, utilisez weak dans les motifs de délégation et les closures. Pour Kotlin, utilisez WeakReference ou des composants Lifecycle-aware qui effacent automatiquement les références lors de la destruction du propriétaire. Dans les deux approches, l’objectif est le même — éliminer les références fortes là où elles créent une chaîne de rétention incassable.
Foire aux questions
Strong Reference augmente le retain count de l’objet et empêche sa libération tant que la référence existe. Weak Reference ne modifie pas le retain count et devient automatiquement nil lorsque l’objet est supprimé de la mémoire. Les références fortes sont utilisées pour la propriété, les références faibles pour les connexions inverses et les délégués.
Retain cycle — un verrouillage mutuel dans lequel deux objets se maintiennent l’un l’autre avec des références fortes. Leur retain count ne tombe jamais à zéro, la mémoire n’est pas libérée. Cela entraîne des fuites mémoire : les objets restent dans le tas pour toujours, l’application consomme de plus en plus de ressources et finit par planter avec OutOfMemory.
Utilisez Instruments Leaks depuis Xcode — lancez le profilage avec le modèle Leaks, exécutez un scénario dans l’application et vérifiez les indicateurs de fuite. Pour un diagnostic précis, passez à l’onglet Cycles & Roots — il montre le graphe des références fortes mutuelles qui forment un cycle incassable.
Unowned doit être utilisée lorsque la durée de vie de l’objet enfant ne dépasse garantiement pas celle du parent — par exemple, lors de la liaison d’un objet à une portée strictement définie. En cas de doute, utilisez Weak, car l’accès à une référence unowned libérée provoque un crash de l’application.
Indirectement — oui. Chaque retain et release dans ARC est une opération atomique avec une surcharge. Avec un grand nombre d’objets dans des cycles, cela peut affecter les performances. Cependant, le principal problème n’est pas la vitesse d’ARC, mais les fuites mémoire dues à un type de référence mal choisi.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.