Sobes.tech
Middle

Ako funguje automatické riadenie pamäte (ARC) v Swift?

sobes.tech AI

Odpoveď od AI

ARC (Automatické počítanie referencií) vo Swiftu sleduje a spravuje využívanie pamäte vašich aplikácií. Automaticky uvoľňuje pamäť, ktorú používajú inštancie tried, keď už nie sú potrebné, čím zabraňuje únikom pamäte.

Pri vytváraní novej inštancie triedy ARC pridelení blok pamäte na uloženie informácií o tejto inštancii. Keď inštancia už nie je potrebná, ARC uvoľní túto pamäť, aby mohla byť použitá na iné účely.

ARC funguje tak, že počíta počet silných odkazov (strong references) na každú inštanciu triedy. Silný odkaz je odkaz, ktorý drží inštanciu v pamäti. Keď počet silných odkazov na inštanciu klesne na nulu, ARC uvoľní s ňou spojenú pamäť.

Vo Swift existujú tri typy odkazov, ktoré ovplyvňujú ARC:

  1. Silné odkazy (Strong References): zvyšujú čítač odkazov na inštanciu.
  2. Slabé odkazy (Weak References): neovplyvňujú čítač odkazov. Používajú sa na zabránenie cyklov silných odkazov, keď dve inštancie triedy držia navzájom silné odkazy. Slabý odkaz sa automaticky stáva nil pri deinitializácii objektu, na ktorý ukazuje.
  3. Nepovolené odkazy (Unowned References): tiež neovplyvňujú čítač odkazov. Používajú sa, keď ste si istí, že odkazovaný objekt bude vždy existovať, kým existuje aktuálny objekt. Nemôžu byť voliteľné (nil). Pokus o prístup k deinitializovanému objektu cez nepovolený odkaz spôsobí chybu za behu.

Príklad cyklu silných odkazov:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) je inicializovaný")
    }

    deinit {
        print("\(name) je deinitializovaný")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Byt \(unit) je inicializovaný")
    }

    deinit {
        print("Byt \(unit) je deinitializovaný")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person sa ne deinitializuje
unit4A = nil // Apartment sa ne deinitializuje

Na vyriešenie cyklu použite slabé alebo nepovolené odkazy. V tomto prípade by tenant v Apartment mal byť slabý odkaz:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) je inicializovaný")
    }

    deinit {
        print("\(name) je deinitializovaný")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Použitie slabého odkazu

    init(unit: String) {
        self.unit = unit
        print("Byt \(unit) je inicializovaný")
    }

    deinit {
        print("Byt \(unit) je deinitializovaný")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person sa deinitializuje
unit4A = nil // Apartment sa deinitializuje

Voľba medzi slabým a nepovoleným odkazom závisí od vzťahu medzi objektmi:

  • Slabý: Ak jeden z objektov môže byť nil skôr, než sa druhý deinitializuje (opcionálna hodnota).
  • Nepovolený: Ak jeden z objektov vždy existuje, kým existuje druhý (neopcionálna hodnota).

ARC tiež spravuje pamäť používanú uzávierkami (closures), ktoré zachytávajú inštancie tried. Cyklus silných odkazov môže vzniknúť, keď uzávierka zachytí inštanciu triedy a táto inštancia má tiež silnú referenciu na uzávierku. Na zabránenie toho sa používajú zoznamy zachytenia (capture lists) v uzávierkach.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        // Bez zoznamu zachytenia, možný silný cyklus zachytenia
        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) je deinitializovaný")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement sa ne deinitializuje

Riešenie pomocou zoznamu zachytenia [weak self]:

class AnHTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Použitie weak self
        guard let self = self else { return "" } // Kontrola, že self existuje
        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) je deinitializovaný")
    }
}

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement sa deinitializuje

Použitie [unowned self] je možné, ak ste si istí, že inštancia triedy (self) nebude nil pred vykonaním uzávierky.

ARC výrazne zjednodušuje správu pamäti vo Swift v porovnaní s ručnou správou, ale vyžaduje pochopenie typov odkazov na zabránenie únikom pamäti.