Retain Cycle — lényege, kialakulásának okai és megszüntetése alkalmazásfejlesztésben

Szerző: IT Sectr Megjelenés: 2026-03-29 Olvasási idő: 8 perc

Retain Cycle (ciklikus hivatkozás) — helyzet az ARC-ben, amikor két vagy több objektum strong referenciákon keresztül hivatkozik egymásra, zárt ciklust alkotva. A Apple Memory Management Guide, 2026 szerint a retain cycle blokkolja a ciklus összes objektumának felszabadítását, mivel mindegyik rendelkezik retain count ≥ 1 értékkel. Ellentétben a GC memóriaszivárgásával, a retain cycle garantáltan életben tartja az objektumokat, amíg a ciklus legalább egy külső résztvevője él — és még az összes külső hivatkozás elvesztése után is, ha a ciklus izolált.

Fő pontok

  • Retain Cycle — zárt strong referencia lánc, amelyben az objektumok nem szabadíthatók fel az ARC által
  • Ok — két (vagy több) objektum strong referenciát tart egymásra, a retain count nullázása lehetetlen
  • Következmények — memóriaszivárgás: az objektumok örökre a memóriában maradnak, nő a RAM fogyasztás
  • Megoldás — a ciklusban lévő egyik strong referencia cseréje weak vagy unowned-re
  • Diagnosztika — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Mi az a Retain Cycle?

Retain Cycle — olyan helyzet, amikor két vagy több objektum strong referenciákon keresztül birtokolja egymást, zárt függőségi gráfot létrehozva. Az ARC egyik objektumot sem tudja felszabadítani, mert mindegyik retain count mindig ≥ 1: A objektum tartja B-t, B tartja A-t, és számlálóik soha nem nullázódnak.

A probléma kizárólag referenciaszámláló rendszerekben (ARC, MRR) fordul elő. A Garbage Collection-ben a gyűjtő a gyökérhalmazból (root set) kiinduló referencia gráf alapján határozza meg az elérhetetlenséget — a ciklusok nem jelentenek akadályt. Az ARC-ben azonban egy ciklus szivárgással egyenértékű, mert a determinisztikus felszabadítás a számláló alapján nem tudja feloldani a körkörös függőséget.

A WWDC 2012 Session 406 szerint a retain cycle a leggyakoribb oka a memóriaszivárgásoknak Objective-C és Swift alkalmazásokban. Tipikus forgatókönyvek: parent-child kapcsolatok delegáltakkal, self-et befogó lezárások és réteges architektúrák kétirányú kapcsolatokkal.

Példák retain cycle-re iOS fejlesztésben

Tekintsük át a klasszikus retain cycle forgatókönyveket, amelyekkel minden iOS fejlesztő találkozik. Ezen minták megértése az ARC-vel való biztonságos kód írásának alapja.

Parent-Child delegálttal

Klasszikus forgatókönyv: a szülő objektum (pl. UIViewController) létrehoz egy gyermek objektumot, és annak delegáltjává válik. Ha mindkettő strong referenciát használ, retain cycle jön létre. Megoldás — a delegáltnak weak-nek kell lennie.

swift
// HIBA: retain cycle strong delegate-en keresztül
protocol ChildDelegate: AnyObject { }

class ParentVC: UIViewController, ChildDelegate {
    var child: ChildVC?

    func showChild() {
        child = ChildVC()
        child?.delegate = self        // Parent → Child (strong)
    }                                 // Child → Parent (strong delegate-en keresztül)
}                                     // ⚠️ Retain cycle!

class ChildVC: UIViewController {
    var delegate: ChildDelegate?    // ❌ strong alapértelmezett
}

// JAVÍTÁS: weak delegate
class ChildVC: UIViewController {
    weak var delegate: ChildDelegate? // ✅ weak — nem tartja
}

A példában a ParentVC strong referenciát tart a ChildVC-re a child tulajdonságon keresztül. A ChildVC strong referenciát tart a ParentVC-re a delegate-en keresztül. A ciklus zárt. Javítás: weak var delegate — a referencia nem növeli a retain count-ot, és a ParentVC felszabadítható.

NSTimer és retain cycle

NSTimer — a retain cycle klasszikus forrása. A timer tartja a target-et (általában self), a target pedig a timer-t egy tulajdonságon keresztül. Még ha a timer egyszer használatos is, nem szabadul fel az invalidate-ig. Megoldás: mindig hívd meg a timer.invalidate()-et a deinit-ben vagy viewDidDisappear-ben.

Réteges architektúrák

A kaszkádolt tulajdonlású architektúrákban (koordinátorok, routerek) gyakran több lépcsős ciklusok keletkeznek: Coordinator → ViewController → ViewModel → Coordinator (callback-en keresztül). A lánc minden strong referenciáját tudatosan kell kiválasztani — egy weak referencia bármelyik láncszemben megszakítja a ciklust.

Retain Cycle Swift lezárásokban

A Swift lezárásai (closures) strong referenciával fogják be a külső változókat. Ha egy lezárás egy objektum tulajdonságaként van tárolva (pl. completion handler) és befogja a self-et, retain cycle keletkezik: self → closure → self.

Ez a retain cycle leggyakoribb forrása a modern Swift fejlesztésben. Implicit módon jön létre — a fejlesztő nem feltétlenül veszi észre a self befogását a lezárásban, különösen a rövidített szintaxis használatakor explicit self nélkül.

swift
class DownloadService {
    var onComplete: ((Data) -> Void)?
    var result: Data?

    func startDownload() {
        // ❌ Retain cycle: self → onComplete → self
        onComplete = { data in
            self.result = data
            self.notifyUI()
        }

        // ✅ Javítás: capture list weak self-tel
        onComplete = { [weak self] data in
            guard let self else { return }
            self.result = data
            self.notifyUI()
        }
    }

    func notifyUI() { }
}

A capture list [weak self] gyenge referenciát hoz létre a self-re a lezáráson belül. Ha a DownloadService felszabadul a lezárás végrehajtása előtt, a self nil lesz, és a kód biztonságosan kilép a guard-on keresztül. Ez a szabványos minta aszinkron lezárásokhoz Swift-ben — mindig alkalmazni kell, amikor a lezárás tulajdonságként van tárolva.

Unowned self a lezárásokban

unowned self — alternatíva a weak self helyett, amikor a self garantáltan tovább él, mint a lezárás. Példa: azonnal végrehajtódó szinkron lezárás (sorted, filter). Ilyen esetekben a self biztosan él, és az unowned biztonságos. Az unowned azonban crash-el, ha felszabadított objektumhoz fér hozzá — ezért a weak biztonságos választásnak számít alapértelmezésben.

Hogyan észleljük a retain cycle-t: diagnosztikai eszközök

A retain cycle korai észlelése kritikus fontosságú az alkalmazás teljesítménye szempontjából. Tekintsük át a fő eszközöket és módszereket a ciklikus hivatkozások azonosítására iOS fejlesztésben.

Xcode Memory Debugger

Xcode Memory Debugger (Debug Memory Graph) — vizuális eszköz, amely a memóriában lévő objektumok gráfját mutatja a referenciáikkal együtt. A retain cycle a strong nyilak zárt láncaként jelenik meg. Indításhoz: kattints a Debug Memory Graph gombra a Debug area panelen az alkalmazás futása közben. Minden objektum megjelenik típussal, címmel és referencialistával.

Instruments Leaks

Instruments Leaks — profiler a szivárgások automatikus észleléséhez. Rögzíti az allokációkat és valós időben elemzi a referencia gráfot. Nemcsak retain cycle-t, hanem elfelejtett referenciákat, fel nem szabadított ViewController-eket és egyéb szivárgásokat is észlel. A Leaks jelzi a pontos objektumot és a megtartási láncot.

Naplózás deinit

A legegyszerűbb módszer — adj print-et a deinit-hez minden kulcsfontosságú osztályban. Ha a deinit nem hívódik meg az objektum várt megsemmisülésekor — retain cycle van. Ez a módszer nem igényel eszközöket, és hatékony a kezdeti diagnosztikához.

EszközTípusMikor alkalmazandó
Memory DebuggerVizuális gráfKézi ellenőrzés navigáció után
Instruments LeaksAutomatikus elemzésRegressziós tesztelés, CI
deinit printKézi naplózásFejlesztés, code review
Malloc ScribbleRuntime zászlóUse-after-free hibakeresés

Ajánlott megközelítés: használj deinit naplózást a fejlesztési fázisban, Memory Debugger-t — kézi teszteléskor, Instruments Leaks-ot — a CI/CD pipeline-ban az automatikus regressziós szivárgás ellenőrzéshez.

Retain cycle megelőzése és best practices

A retain cycle megelőzése könnyebb, mint a javítása éles környezetben. Néhány szabály, amelyek minimalizálják a ciklikus hivatkozások kockázatát.

A weak delegate szabály

Minden delegált és dataSource weak kell legyen. Ez a szabály be van építve az UIKit-be: az Apple SDK összes delegált protokollja weak tulajdonságokkal van deklarálva (UITableView.delegate, UICollectionView.dataSource). Saját protokollokhoz használd a weak var delegate: MyDelegate?-t és származtasd a protokollt az AnyObject-ből.

Capture list a lezárásokban

Minden lezárás, amely tulajdonságként van tárolva (completion handler, callback) és befogja a self-et, [weak self]-et kell használnia a capture list-ben. Kivétel — azonnal végrehajtódó és nem tárolt lezárások (sorted, map, filter). Ezeknél az unowned self biztonságos.

Architektúra ellenőrzése

Összetett architektúrákban (VIPER, Coordinators, Redux) kövesd a strong referenciák irányát. A tulajdonos strong referenciát tart a beosztottra, de a beosztott csak weak vagy unowned keresztül hivatkozhat a tulajdonosra. Az egyirányú adatfolyam (unidirectional data flow) leegyszerűsíti a referenciák ellenőrzését.

swift
// Példa: ellenőrzés deinit naplózással
class BaseViewController: UIViewController {
    deinit {
        print("✅ \(type(of: self)) deallocated")
    }
}

// Használat: minden ViewController a BaseViewController-ből származik
class ProfileVC: BaseViewController {
    var viewModel: ProfileViewModel?
    var onLogout: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onLogout = { [weak self] in
            self?.dismiss(animated: true)
        }
    }
}
// A ProfileVC bezárásakor „✅ ProfileVC deallocated” üzenetet várunk a konzolban

A deinit naplózással ellátott alaposztály azonnali visszajelzést ad. Ha az üzenet nem jelenik meg a képernyő várt bezárásakor — ebben az osztályban retain cycle van. Add hozzá ezt a gyakorlatot a projekt sablonhoz minden ViewController számára.

Gyakran Ismételt Kérdések

Miben különbözik a retain cycle a GC memóriaszivárgásától?

Retain cycle — az ARC specifikus problémája, ahol a strong referenciák zárt köre blokkolja a felszabadítást. GC-ben a gyűjtő a gyökérből (root set) való elérhetőséget elemzi, nem a referenciaszámlálót — ezért a ciklusok nem minősülnek szivárgásnak. Az ARC-ben viszont minden izolált ciklus garantált szivárgás.

Hogyan szakítja meg a weak referencia a retain cycle-t?

A Weak referencia nem növeli az objektum retain count-ját. Ha a ciklusban lévő egyik strong referenciát weak-re cseréled, minden objektum retain count-ja nullázható. Az objektum felszabadítása után a weak referencia automatikusan nil-re állítódik, megakadályozva a holt memóriához való hozzáférést.

Állhat-e a retain cycle három vagy több objektumból?

Igen, a retain cycle bármennyi objektumot tartalmazhat: A → B → C → A. A felszabadításhoz elég megszakítani egy láncszemet a ciklusban — cserélj bármelyik strong referenciát weak vagy unowned-re. Az eszközök a teljes gráfot mutatják, nem csak objektumpárokat.

Miért nem hoz létre retain cycle-t a GCD DispatchWorkItem?

A GCD (Grand Central Dispatch) nem tárolja a lezárást a végrehajtás után. A DispatchWorkItem végrehajtódik és felszabadul, még akkor is, ha a lezárás befogja a self-et. Retain cycle csak akkor keletkezik, amikor a lezárás tulajdonságként van tárolva (completion handler egy osztályban), nem pedig amikor egy sorba kerül átadásra.

Milyen típusú retain cycle-ket nem észlel az Instruments?

Az Instruments Leaks nem mindig találja meg az ideiglenes retain cycle-ket (amelyek másodpercekig léteznek) és a C/C++ objektumokban lévő ciklikus hivatkozásokat bridge-en keresztül. Teljes ellenőrzéshez használj kézi Memory Debugger-t + a scene összes kulcsfontosságú objektumának deinit naplózását.

Összefoglaló

  • Retain Cycle — zárt strong referencia lánc, amely blokkolja az objektumok felszabadítását ARC-ben
  • Okok — delegáltak strong referenciával, self-et befogó lezárások, kétirányú parent-child kapcsolatok
  • Megoldás — egy strong referencia cseréje weak vagy unowned-re megszakítja a ciklust
  • Lezárások — a tárolt completion handler-ek mindig [weak self]-et használjanak
  • Delegáltak — mindig weak; a delegált protokollnak AnyObject-ből kell származnia
  • Diagnosztika — Xcode Memory Debugger, Instruments Leaks, deinit naplózás
  • Megelőzés — egyirányú adatfolyam, weak delegate, capture list, alaposztály deinit-tel

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is