Sobes.tech
Middle

Co jsou to cykly udržení (retain cycles) v iOS?

sobes.tech AI

Odpověď od AI

Cyklus uchování vznikají, když dva nebo více objektů mají silné odkazy na sebe navzájem, čímž vytvářejí "uzavřený kruh". V důsledku toho počet odkazů na každý objekt nikdy nedosáhne nuly a nemůže být uvolněn ARC (Automatic Reference Counting). To vede k únikům paměti, protože objekty zůstávají v paměti i po přestání být používány.

Příklad:

class Person {
    let jméno: String
    var apartment: Apartment?

    init(jméno: String) {
        self.jméno = jméno
        print("\(jméno) je inicializováno")
    }

    deinit {
        print("\(jméno) je uvolněno")
    }
}

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

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

    deinit {
        print("Apartment \(jednotka) je uvolněno")
    }
}

V tomto příkladu, pokud má Person silnou referenci na Apartment a Apartment má silnou referenci na Person, při pokusu o uvolnění těchto objektů nebudou odstraněny, protože jejich počítadla odkazů nedosáhnou nuly.

Jak problém cyklů uchování vyřešit:

Použijte slabé (weak) nebo nepřipojené (unowned) odkazy.

  • weak: Používá se, když je možné, že související objekt se stane nil během životního cyklu jiného objektu. Odkaz se automaticky stane nil, když je objekt, na který odkazuje, uvolněn. Je volitelný.
  • unowned: Používá se, když je známo, že související objekt má stejný nebo delší životní cyklus než objekt, který na něj odkazuje. Nemůže být nil.

Opravený příklad s použitím weak:

Změníme odkaz tenant v třídě Apartment na slabý:

class Apartment {
    let jednotka: String
    weak var tenant: Person? // používáme weak

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

    deinit {
        print("Apartment \(jednotka) je uvolněno")
    }
}

Nyní, když je objekt Person uvolněn, odkaz tenant v Apartment se automaticky stane nil, čímž se přeruší cyklus uchování a umožní uvolnění Apartment také.

Porovnání weak a unowned:

Charakteristika weak unowned
Typ odkazu Volitelný Nevolitelný
Může být nil? Ano, při uvolnění objektu Ne
Kdy použít? Když může být objekt nil Když objekt nemůže být nil
Výkon O něco pomalejší kvůli kontrole nil O něco rychlejší

Také, cykly uchování se mohou vyskytnout v uzávěrkách (closures).

Když uzávěr zachytí vlastnosti instance nebo self, může vytvořit silnou referenci na objekt. Pokud má tento objekt také silnou referenci na uzávěr (například přes vlastnost uzávěru nebo delegáta), vzniká cyklus uchování.

Jak problém cyklů uchování v uzávěrkách vyřešit:

Použijte seznam zachycení (capture list).

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // seznam zachycení s weak self
            guard let self = self else { return } // kontrola na `nil` pro weak self
            self.value += 1
            print("Hodnota je nyní \(self.value)")
        }
    }
}

V tomto příkladu [weak self] vytváří slabou referenci na self uvnitř uzávěru, čímž se předchází cyklu uchování.