Sobes.tech
Middle

Cum se poate preveni crearea unui ciclu de retenție (retain cycle) în iOS?

sobes.tech AI

Răspuns de la AI

Ciclurile de retenție sunt prevenite prin utilizarea corectă a referințelor slabe (weak) sau necondiționate (unowned).

Referințe slabe (weak):

  • Nu cresc numărătorul de referințe al obiectului.
  • Pot deveni nil (sunt nullable).
  • Sunt utilizate atunci când un obiect poate exista independent de altul (de exemplu, un delegat).
class DelegateExample {
    weak var delegate: SomeDelegateType? // Referință slabă către delegat
}

Referințe necondiționate (unowned):

  • Nu cresc numărătorul de referințe al obiectului.
  • Sunt garantat non-nule (non-nil).
  • Sunt utilizate atunci când un obiect deține explicit altul, dar obiectul dependent poate exista doar împreună cu proprietarul (de exemplu, closures care capturează self într-o clasă, unde closure-ul "aparține" explicit instanței clasei). Accesarea unei referințe unowned când obiectul a fost deja eliberat va cauza o eroare la runtime.
class Owner {
    var closure: (() -> Void)?

    func setupClosure() {
        closure = { [unowned self] in // Referință necondiționată `unowned` către self
            // Utilizarea lui self
            print("Utilizare `unowned` self")
        }
    }
}

Utilizare în closures: Closures pot crea cicluri de retenție dacă capturează instanțe de clase prin referințe puternice, iar acea instanță de clasă conține și o referință puternică către closure. Pentru a evita acest lucru, se folosesc liste de captură (capture lists) la începutul closure-ului:

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

    lazy var asHTML: () -> String = { [weak self] in // Referință slabă către self
        guard let strongSelf = self else {
            return "" // Tratarea cazului în care self este deja nil
        }
        if let text = strongSelf.text {
            return "<\(strongSelf.name)>\(text)</\(strongSelf.name)>"
        } else {
            return "<\(strongSelf.name)/>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) este fiind deinitializat")
    }
}

Alegerea între weak și unowned depinde de semantica relațiilor dintre obiecte:

  • weak: când referința poate deveni nil pe parcursul ciclului de viață al obiectului referit.
  • unowned: când obiectul referit există garantat mai mult sau egal cu cel care îl referă, și referința nu va deveni niciodată nil.