Sobes.tech
Middle

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:

  1. Referințe puternice (Strong References): Crește contorul de referințe al instanței.
  2. 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 nil la deinițializarea obiectului la care se referă.
  3. 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.