Sobes.tech
Middle

Šta su ciklusi zadržavanja (retain cycles) u iOS?

sobes.tech АИ

Одговор од АИ

Цикли zadržavanja nastaju kada dva ili više objekata imaju jake reference jedan na drugog, stvarajući "zatvoreni krug". Kao rezultat toga, brojač referenci svakog objekta nikada ne dostiže nulu i on ne može biti oslobođen od ARC (Automatic Reference Counting). To dovodi do curenja memorije, jer objekti ostaju u memoriji čak i nakon što više nisu u upotrebi.

Primer:

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

    init(name: String) {
        self.name = name
        print("\(name) se inicijalizuje")
    }

    deinit {
        print("\(name) se oslobađa")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) se inicijalizuje")
    }

    deinit {
        print("Apartment \(unit) se oslobađa")
    }
}

U ovom primeru, ako Person ima jaku referencu na Apartment i Apartment ima jaku referencu na Person, prilikom pokušaja oslobađanja ovi objekti neće biti obrisani, jer njihovi brojači referenci ne dostižu nulu.

Kako rešiti problem cikličnih referenci:

Koristite slabe (weak) ili neposredne (unowned) reference.

  • weak: Koristi se kada je moguće da povezani objekat postane nil tokom životnog ciklusa drugog objekta. Referenca automatski postaje nil kada se objekat na koji se odnosi oslobodi. To je opcionalno.
  • unowned: Koristi se kada je poznato da povezani objekat ima isti ili duži životni vek od objekta koji na njega ukazuje. Ne može biti nil.

Ispravljeni primer sa weak:

Promenimo referencu tenant u klasi Apartment u slabu:

class Apartment {
    let unit: String
    weak var tenant: Person? // koristimo weak

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) se inicijalizuje")
    }

    deinit {
        print("Apartment \(unit) se oslobađa")
    }
}

Sada, kada se Person objekat oslobodi, referenca tenant u Apartment automatski postaje nil, prekinuvši ciklus zadržavanja i omogućavajući da i Apartment bude oslobođen.

Uporedba weak i unowned:

Karakteristika weak unowned
Tip reference Opcionalni Neopcionalni
Može li biti nil? Da, kada se objekat oslobodi Ne
Kada koristiti? Kada objekat može postati nil Kada objekat ne može biti nil
Performanse Malo sporije zbog provere nil Malo brže

Takođe, ciklusi zadržavanja mogu nastati i u zatvaranjima (closures).

Kada zatvaranje uhvati svojstva instance ili self, može stvoriti jaku referencu na objekat. Ako taj objekat takođe ima jaku referencu na zatvaranje (npr. putem svojstva-zatvaranja ili delegata), nastaje ciklus zadržavanja.

Kako rešiti problem ciklusa zadržavanja u zatvaranjima:

Koristite listu za hvatanje (capture list).

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // lista za hvatanje sa weak self
            guard let self = self else { return } // provera na `nil` za weak self
            self.value += 1
            print("Vrednost je sada \(self.value)")
        }
    }
}

U ovom primeru, [weak self] stvara slab referencu na self unutar zatvaranja, sprečavajući ciklus zadržavanja.