Comment fonctionne la gestion automatique de la mémoire (ARC) en Swift?
sobes.tech IA
Réponse de l'IA
ARC ( comptage automatique de références) en Swift suit et gère l’utilisation de la mémoire par vos applications. Il libère automatiquement la mémoire utilisée par les instances de classes lorsqu’elles ne sont plus nécessaires, évitant ainsi les fuites de mémoire.
Lors de la création d’une nouvelle instance de classe, ARC alloue un bloc de mémoire pour stocker des informations sur cette instance. Lorsqu’elle n’est plus nécessaire, ARC libère cette mémoire pour qu’elle puisse être réutilisée.
ARC fonctionne en comptant le nombre de références fortes (strong references) à chaque instance de classe. Une référence forte maintient l’instance en mémoire. Quand le nombre de références fortes atteint zéro, ARC libère la mémoire associée.
Il existe trois types de références en Swift qui influencent ARC :
- Références fortes (Strong References) : Augmentent le compteur de références de l’instance.
- Références faibles (Weak References) : N’augmentent pas le compteur de références. Utilisées pour éviter les cycles de références fortes, lorsque deux instances de classes se maintiennent mutuellement par des références fortes. La référence faible devient automatiquement
nillors de la désinitialisation de l’objet. - Références non possédantes (Unowned References) : N’augmentent pas non plus le compteur de références. Utilisées lorsque vous êtes sûr que l’instance référencée existera toujours tant que l’instance courante. Ne peuvent pas être optionnelles (
nil). Accéder à un objet désinitialisé via une référence non possédante entraînera une erreur d’exécution.
Exemple de cycle de références fortes :
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) est en cours d’initialisation")
}
deinit {
print("\(name) est en train d’être désinitialisé")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Appartement \(unit) est en cours d’initialisation")
}
deinit {
print("Appartement \(unit) est en train d’être désinitialisé")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil // Person n’est pas désinitialisé
unit4A = nil // Appartement n’est pas désinitialisé
Pour résoudre le problème de cycle, utilisez des références faibles ou non possédantes. Dans ce cas, tenant dans Apartment doit être une référence faible :
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) est en cours d’initialisation")
}
deinit {
print("\(name) est en train d’être désinitialisé")
}
}
class Apartment {
let unit: String
weak var tenant: Person? // Utilisation de référence faible
init(unit: String) {
self.unit = unit
print("Appartement \(unit) est en cours d’initialisation")
}
deinit {
print("Appartement \(unit) est en train d’être désinitialisé")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil // Person est désinitialisé
unit4A = nil // Appartement est désinitialisé
Le choix entre référence faible et non possédante dépend de la relation entre les objets :
- Faible (Weak) : Si l’un des objets peut devenir
nilavant que l’autre ne soit désinitialisé (valeur optionnelle). - Non possédant (Unowned) : Si l’un des objets existe toujours tant que l’autre existe (valeur non optionnelle).
ARC gère également la mémoire utilisée par les closures qui capturent des instances de classes. Des cycles de références fortes peuvent survenir lorsque une closure capture une instance de classe et que cette instance a une référence forte à la closure. Pour éviter cela, on utilise des listes de capture (capture lists) dans les closures.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
// Pas de liste de capture, cycle potentiel
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name)>"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) est en train d’être désinitialisé")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "bonjour, le monde")
print(paragraph!.asHTML())
paragraph = nil // HTMLElement n’est pas désinitialisé
Solution avec liste de capture [weak self] :
class AnHTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [weak self] in // Utilisation de weak self
guard let self = self else { return "" } // Vérification de l’existence de self
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name)>"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) est en train d’être désinitialisé")
}
}
var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "bonjour, le monde")
print(paragraph!.asHTML())
paragraph = nil // AnHTMLElement est désinitialisé
L’utilisation de [unowned self] est possible si vous êtes sûr que l’instance de la classe (self) ne deviendra pas nil avant l’exécution de la closure.
ARC simplifie grandement la gestion de la mémoire en Swift par rapport à la gestion manuelle, mais il nécessite une compréhension des types de références pour éviter les fuites de mémoire.