Sobes.tech
Middle

Jak funguje automatické řízení paměti (ARC) ve Swiftu?

sobes.tech AI

Odpověď od AI

ARC (Automatické počítání odkazů) ve Swiftu sleduje a spravuje využití paměti vašich aplikací. Automaticky uvolňuje paměť používanou instancemi tříd, když již nejsou potřeba, čímž předchází únikům paměti.

Při vytváření nové instance třídy ARC alokuje blok paměti pro uložení informací o této instanci. Když instance již není potřeba, ARC uvolní tuto paměť, aby mohla být použita pro jiné účely.

ARC funguje tak, že počítá počet silných odkazů (strong references) na každou instanci třídy. Silný odkaz je odkaz, který drží instanci v paměti. Když počet silných odkazů na instanci klesne na nulu, ARC uvolní s ní spojenou paměť.

Ve Swiftu existují tři typy odkazů, které ovlivňují ARC:

  1. Silné odkazy (Strong References): zvyšují čítač odkazů na instanci.
  2. Slabé odkazy (Weak References): nezvyšují čítač odkazů. Používají se k zabránění cyklům silných odkazů, když dvě instance třídy drží navzájem silné odkazy. Slabý odkaz se automaticky stává nil při deinitializaci odkazu na objekt.
  3. Nepřidělené odkazy (Unowned References): také nezvyšují čítač odkazů. Používají se, když jste si jisti, že odkazovaná instance bude vždy existovat, dokud existuje aktuální instance. Nemohou být volitelné (nil). Pokus o přístup k deinitializovanému objektu přes nepřidělený odkaz povede k chybě za běhu.

Příklad cyklu silných odkazů:

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

    init(name: String) {
        self.name = name
        print("\(name) je inicializován")
    }

    deinit {
        print("\(name) je deinitializován")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) je inicializován")
    }

    deinit {
        print("Apartment \(unit) je deinitializován")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person není deinitializován
unit4A = nil // Apartment není deinitializován

Pro řešení cyklu použijte slabé nebo nepřidělené odkazy. V tomto případě by tenant v Apartment měl být slabý odkaz:

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

    init(name: String) {
        self.name = name
        print("\(name) je inicializován")
    }

    deinit {
        print("\(name) je deinitializován")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) je inicializován")
    }

    deinit {
        print("Apartment \(unit) je deinitializován")
    }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil // Person je deinitializován
unit4A = nil // Apartment je deinitializován

Volba mezi slabým a nepřiděleným odkazem závisí na vztahu mezi objekty:

  • Slabý: Pokud jeden z objektů může být nil dříve, než se druhý deinitializuje (volitelná hodnota).
  • Nepřidělený: Pokud jeden z objektů vždy existuje, dokud druhý (nevolitelná hodnota).

ARC také spravuje paměť používanou uzávěry (closures), které zachycují instance tříd. Cyklus silných odkazů může vzniknout, když uzávěr zachytí instanci třídy a tato instance má také silnou odkaz na uzávěr. K tomu slouží seznamy zachycení (capture lists) v uzávěrech.

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

    lazy var asHTML: () -> String = {
        // Bez seznamu zachycení, možný silný cyklus zachycení
        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 deinitializován")
    }
}

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

paragraph = nil // HTMLElement není deinitializován

Řešení pomocí seznamu zachycení [weak self]:

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

    lazy var asHTML: () -> String = { [weak self] in // Použití weak self
        guard let self = self else { return "" } // Kontrola existence 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) je deinitializován")
    }
}

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

paragraph = nil // AnHTMLElement je deinitializován

Použití [unowned self] je možné, pokud jste si jisti, že instance třídy (self) nebude nil před provedením uzávěru.

ARC výrazně zjednodušuje správu paměti ve Swiftu ve srovnání s ruční správou, ale vyžaduje pochopení typů odkazů, aby se předešlo únikům paměti.