Cum funcționează gestionarea automată a memoriei (ARC) în Swift?
sobes.tech AI
Răspuns de la AI
ARC (Automatic Reference Counting) în Swift urmărește și gestionează utilizarea memoriei de către aplicațiile tale. El eliberează automat memoria utilizată de instanțele de clase atunci când acestea nu mai sunt necesare, prevenind scurgerile de memorie.
La crearea unei noi instanțe de clasă, ARC alocă un bloc de memorie pentru a stoca informații despre acea instanță. Când instanța nu mai este necesară, ARC eliberează acea memorie pentru a putea fi utilizată în alte scopuri.
ARC funcționează numărând numărul de referințe puternice (strong references) către fiecare instanță de clasă. O referință puternică este o referință care menține instanța în memorie. Când numărul de referințe puternice ajunge la zero, ARC eliberează memoria asociată.
În Swift există trei tipuri de referințe care influențează ARC:
- Referințe puternice (Strong References): Crește contorul de referințe al instanței.
- Referințe slabe (Weak References): Nu cresc contorul de referințe. Sunt folosite pentru a evita ciclurile de referințe puternice atunci când două instanțe de clasă se referă reciproc. Referințele slabe devin automat
nilla deinițializarea obiectului la care se referă. - Referințe neasigurate (Unowned References): De asemenea, nu cresc contorul de referințe. Sunt folosite atunci când ești sigur că instanța la care se face referire va exista întotdeauna atâta timp cât există instanța curentă. Nu pot fi opționale (
nil). Încercarea de a accesa un obiect deinițializat printr-o referință neasigurată va duce la o eroare de execuție.
Exemplu de ciclu de referințe puternice:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) este inițializat")
}
deinit {
print("\(name) este deinițializat")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartament \(unit) este inițializat")
}
deinit {
print("Apartament \(unit) este deinițializat")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil // Person nu este deinițializat
unit4A = nil // Apartament nu este deinițializat
Pentru a rezolva problema ciclului, folosiți referințe slabe sau neasigurate. În acest caz, tenant în Apartment trebuie să fie o referință slabă:
class Person {
let name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) este inițializat")
}
deinit {
print("\(name) este deinițializat")
}
}
class Apartment {
let unit: String
weak var tenant: Person? // Referință slabă
init(unit: String) {
self.unit = unit
print("Apartament \(unit) este inițializat")
}
deinit {
print("Apartament \(unit) este deinițializat")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil // Person este deinițializat
unit4A = nil // Apartament este deinițializat
Alegerea între referințe slabe și neasigurate depinde de relația dintre obiecte:
- Weak: Dacă unul dintre obiecte poate deveni
nilînainte ca celălalt să fie deinițializat (valoare opțională). - Unowned: Dacă unul dintre obiecte există întotdeauna atât timp cât celălalt (valoare neopțională).
ARC gestionează, de asemenea, memoria utilizată de closure-uri care prind instanțe de clase. Ciclurile de referințe puternice pot apărea atunci când un closure prinde o instanță de clasă, iar această instanță are, de asemenea, o referință puternică către closure. Pentru a preveni acest lucru, se folosesc liste de captură (capture lists) în closure-uri.
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
// Fără listă de captură, posibil ciclu de referințe
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) este deinițializat")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "salut, lume")
print(paragraph!.asHTML())
paragraph = nil // HTMLElement nu este deinițializat
Soluția cu [weak self]:
class AnHTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = { [weak self] in // Folosirea weak self
guard let self = self else { return "" } // Verificare existență 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) este deinițializat")
}
}
var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "salut, lume")
print(paragraph!.asHTML())
paragraph = nil // AnHTMLElement este deinițializat
Utilizarea [unowned self] este posibilă dacă ești sigur că instanța clasei (self) nu va deveni nil înainte de executarea closure-ului.
ARC simplifică gestionarea memoriei în Swift comparativ cu gestionarea manuală, dar necesită înțelegerea tipurilor de referințe pentru a preveni scurgerile de memorie.