Automatic Reference Counting (ARC) est un système de gestion de mémoire en Swift et Objective-C qui compte automatiquement le nombre de références à chaque objet et le libère lorsque le compteur atteint zéro. Selon la Documentation Apple Swift, 2026, ARC est intégré au compilateur et fonctionne à la compilation, en insérant des appels retain/release aux endroits appropriés. Contrairement au Garbage Collection, ARC ne nécessite pas de thread de collecte séparé et ne crée pas de pauses pendant l'exécution de l'application.
Points essentiels
ARC (Automatic Reference Counting) est un mécanisme de gestion de mémoire basé sur le compilateur introduit par Apple dans Xcode 4.2 (2011) pour Objective-C et hérité par Swift. Contrairement à la gestion manuelle de mémoire (Manual Retain-Release, MRR), ARC automatise entièrement les appels retain, release et autorelease, en les insérant à la compilation sans intervention du développeur.
ARC n'est pas un ramasse-miettes. C'est une analyse statique avec insertion dynamique de code : le compilateur analyse les durées de vie des objets et place retain/release aux points où les objets sont créés, copiés ou sortent de portée. Le résultat est une libération déterministe de la mémoire : l'objet est supprimé exactement au moment où il n'a plus de références, sans délai ni pause.
Selon la WWDC 2011 Session 323, le passage de MRR à ARC a réduit de 70 % les bogues de crash liés à la mémoire dans les applications Apple. Les développeurs ont cessé d'équilibrer manuellement retain/release, éliminant toute une classe de fuites et d'erreurs double-free.
Chaque objet en mémoire possède un compteur de références (retain count). Lorsqu'un objet est créé, le compteur est défini à 1. Lorsqu'une nouvelle référence strong pointe vers l'objet, le compteur augmente (retain). Lorsqu'une référence strong disparaît, le compteur diminue (release). À zéro, l'objet est immédiatement libéré.
Le compilateur Swift n'insère pas retain/release à chaque affectation — il utilise l'analyse statique pour l'optimisation. Par exemple, si un objet est garanti de ne pas être utilisé après avoir été passé, le compilateur peut sauter un release/retain inutile. Cette optimisation s'appelle ARC Optimization.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q sort de portée
// retain count = 1
// p sort de portée
// retain count = 0 → deinit
}
Cet exemple montre comment ARC gère le compteur : lors de l'affectation q = p, le compteur augmente ; lorsque q sort de portée, il diminue. Lorsque la dernière référence strong disparaît, le déinitialiseur est appelé immédiatement. Aucun ramasse-miettes n'attend — la mémoire est libérée sur-le-champ.
ARC et Garbage Collection résolvent le même problème — la gestion automatique de la mémoire — mais avec des approches fondamentalement différentes. Le choix entre eux définit l'architecture du langage : Swift (ARC) vs Java/Go (GC). Examinons les principales différences.
| Caractéristique | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| Moment de libération | Déterministe : immédiatement quand le compteur atteint zéro | Non déterministe : au prochain cycle de collecte |
| Pauses d'exécution | Aucune (retain/release insérés à la compilation) | Pauses Stop-The-World (2–200 ms) |
| Surcharge | Inc/déc du compteur à chaque référence | Parcours du graphe d'objets, marquage, balayage |
| Problèmes | Retain Cycle (résolution manuelle) | Fragmentation du tas, fuites par références oubliées |
| Thread supplémentaire | Non requis | Thread de ramasse-miettes requis |
Le compromis clé : ARC offre des durées de vie prévisibles et zéro pause, mais exige du développeur qu'il comprenne les retain cycles et choisisse correctement weak/unowned. Le GC libère le développeur de ces préoccupations, mais au prix de pauses non déterministes et d'un thread supplémentaire.
ARC définit trois types de qualificateurs de référence, chacun affectant le compteur et le cycle de vie de l'objet différemment. Choisir le bon qualificateur est la base d'une gestion sûre de la mémoire en Swift.
Strong est le qualificateur par défaut. Chaque référence strong augmente le retain count de l'objet de 1. Tant qu'au moins une référence strong existe, l'objet reste vivant. Toutes les propriétés de classe et variables locales en Swift sont strong par défaut. Les références strong créent une relation de possession : l'objet A possède l'objet B.
Weak est une référence qui n'augmente pas le retain count. Un objet peut être libéré même si une référence weak pointe vers lui. Après la libération, la référence weak est automatiquement mise à nil. Les références weak sont toujours déclarées comme var avec un type optionnel (?). Elles sont utilisées pour briser les retain cycles, en particulier dans le modèle delegate.
Unowned est une référence non possessive qui, comme weak, n'augmente pas le retain count. Cependant, une référence unowned n'est pas mise à nil après la libération — accéder à un objet libéré provoque un crash. Unowned est utilisé lorsqu'il est garanti que l'objet vit au moins aussi longtemps que l'objet référent. Les cas typiques sont les closures et les relations parent-enfant avec durée de vie garantie.
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — ne possède pas
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer et CreditCard tous deux libérés — pas de retain cycle
Ici, CreditCard utilise une référence unowned vers Customer. Customer possède la carte (strong), et la carte ne possède pas le client (unowned). Lorsque Customer est libéré, les deux objets sont libérés — aucun retain cycle ne se produit. Si card.customer était strong, le cycle bloquerait la libération.
Malgré l'automatisation, ARC n'est pas une panacée. Les développeurs rencontrent plusieurs problèmes typiques qui nécessitent une compréhension du mécanisme interne de gestion de la mémoire.
Les closures en Swift capturent les variables externes par référence strong. Si une closure est assignée à une propriété de classe et capture self — un retain cycle se produit : la classe retient la closure, la closure retient self. La solution est une liste de capture avec weak ou unowned.
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
La liste de capture [weak self] crée une référence weak vers self à l'intérieur de la closure. Cela brise le retain cycle potentiel. Guard let self garantit que l'objet est vivant avant d'exécuter le code. weak self est la pratique standard pour les closures asynchrones en Swift.
Bien que retain/release soient des opérations légères, dans les boucles intensives, les inc/déc fréquents du compteur ajoutent une surcharge. En Swift 5.9+, le compilateur utilise une optimisation qui supprime les retain/release redondants si l'analyseur prouve qu'ils sont sûrs. Cependant, en Objective-C, retain/release peuvent encore être un goulot d'étranglement dans les scénarios à forte charge avec des millions d'appels par seconde.
Autorelease Pool est un mécanisme de libération différée utilisé en Objective-C et dans certains scénarios Swift. Les objets sont placés dans le pool et reçoivent release lorsque le pool est vidé. Dans les boucles avec de nombreux objets temporaires (par exemple, l'analyse JSON), créer un autoreleasepool personnalisé réduit la consommation maximale de mémoire.
Foire aux questions
Dans la gestion manuelle (MRR), le développeur appelait explicitement retain, release et autorelease. ARC insère ces appels automatiquement à la compilation, éliminant le risque de double-free, les fuites dues à un release oublié et les erreurs d'équilibrage retain/release.
ARC gère uniquement les objets Objective-C et les classes Swift. Pour les structures et pointeurs C/C++, ARC ne s'applique pas — ces objets sont gérés manuellement ou via les pointeurs intelligents C++ (shared_ptr, unique_ptr). Les objets Core Foundation (CFString, CGColor) ne sont pas non plus couverts par ARC.
weak — quand l'objet peut être libéré avant l'objet référent (délégués, closures asynchrones). unowned — quand il est garanti que l'objet vit au moins aussi longtemps que le référent (parent-enfant où l'enfant ne peut pas exister sans le parent). En cas de doute, choisissez weak.
Les types existentiels (protocole comme type) en Swift encapsulent la valeur dans un conteneur spécial (conteneur existentiel). Cela augmente le nombre de retain/release aux frontières des protocoles. En Swift 5.7+, les types de résultat opaques et les paramètres some réduisent la surcharge en éliminant le conteneur.
Il n'y a pas d'API directe pour lire le retain count en Swift — c'est considéré comme un détail d'implémentation. Pour le diagnostic, utilisez Instruments (Allocations, Leaks) ou le Débogueur Mémoire dans Xcode. Ces outils montrent le nombre d'instances de classe vivantes et les chaînes de rétention.
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.