Retain Cycle — esensya, mga sanhi at pag-aayos sa pag-develop ng app

May-akda: IT Sectr Nai-publish: 2026-03-29 Oras ng pagbabasa: 8 min

Retain Cycle (siklikong sanggunian) — sitwasyon sa ARC kung saan dalawa o higit pang mga bagay ay tumutukoy sa isa't isa sa pamamagitan ng strong reference, na bumubuo ng isang saradong siklo. Ayon sa Apple Memory Management Guide, 2026, hinaharangan ng retain cycle ang pagpapalaya ng lahat ng bagay sa siklo, dahil ang bawat isa ay may retain count ≥ 1. Hindi tulad ng leak ng memorya sa GC, tiyak na pinapanatili ng retain cycle na buhay ang mga bagay hangga't buhay ang kahit isang panlabas na kalahok ng siklo — at kahit na matapos mawala ang lahat ng panlabas na sanggunian, kung ang siklo ay nakahiwalay.

Mga Pangunahing Punto

  • Retain Cycle — saradong kadena ng strong reference, kung saan hindi mapapalaya ng ARC ang mga bagay
  • Sanhi — dalawa (o higit pa) bagay ay nagtataglay ng strong reference sa isa't isa, hindi posible ang pag-zero ng retain count
  • Bunga — leak ng memorya: nananatili ang mga bagay sa memorya magpakailanman, tumataas ang konsumo ng RAM
  • Solusyon — palitan ang isa sa mga strong reference sa siklo ng weak o unowned
  • Diagnostiko — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Ano ang Retain Cycle?

Retain Cycle — ay isang sitwasyon kung saan dalawa o higit pang mga bagay ay nagmamay-ari sa isa't isa sa pamamagitan ng strong reference, na lumilikha ng isang saradong graf ng dependency. Hindi mapapalaya ng ARC ang alinman sa mga bagay na ito, dahil ang retain count ng bawat isa ay palaging ≥ 1: bagay A ay humahawak sa B, B ay humahawak sa A, at ang kanilang mga counter ay hindi kailanman nagiging zero.

Ang problema ay lumalabas lamang sa mga sistema na may pagbibilang ng sanggunian (ARC, MRR). Sa Garbage Collection, tinutukoy ng kolektor ang hindi maabot batay sa graf ng sanggunian mula sa root set — ang mga siklo ay hindi hadlang. Sa ARC, ang isang siklo ay katumbas ng leak, dahil ang deterministikong pagpapalaya batay sa counter ay hindi malulutas ang pabilog na dependency.

Ayon sa WWDC 2012 Session 406, ang retain cycle ay ang pinakakaraniwang sanhi ng mga leak ng memorya sa Objective-C at Swift na mga aplikasyon. Mga tipikal na senaryo: mga relasyong parent-child na may mga delegado, closures na kumukuha ng self, at mga layered na arkitektura na may dalawang-daanang koneksyon.

Mga halimbawa ng retain cycle sa iOS development

Tingnan natin ang mga klasikong senaryo ng retain cycle na kinakaharap ng bawat iOS developer. Ang pag-unawa sa mga pattern na ito ay pundasyon para sa pagsulat ng ligtas na code na may ARC.

Parent-Child na may delegado

Klasikong senaryo: ang magulang na bagay (hal. UIViewController) ay lumikha ng anak na bagay at nagiging delegado nito. Kung pareho silang gumagamit ng strong reference, magkakaroon ng retain cycle. Solusyon — ang delegado ay dapat weak.

swift
// ERROR: retain cycle sa pamamagitan ng strong delegate
protocol ChildDelegate: AnyObject { }

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

    func showChild() {
        child = ChildVC()
        child?.delegate = self        // Parent → Child (strong)
    }                                 // Child → Parent (strong sa pamamagitan ng delegate)
}                                     // ⚠️ Retain cycle!

class ChildVC: UIViewController {
    var delegate: ChildDelegate?    // ❌ strong bilang default
}

// PAG-AYOS: weak delegate
class ChildVC: UIViewController {
    weak var delegate: ChildDelegate? // ✅ weak — hindi humahawak
}

Sa halimbawa, ang ParentVC ay nagtataglay ng strong reference sa ChildVC sa pamamagitan ng property na child. Ang ChildVC ay nagtataglay ng strong reference sa ParentVC sa pamamagitan ng delegate. Sarado ang siklo. Pag-aayos: weak var delegate — ang reference ay hindi nagpapataas ng retain count, at ang ParentVC ay maaaring palayain.

NSTimer at retain cycle

NSTimer — isang klasikong pinagmumulan ng retain cycle. Ang timer ay humahawak sa target (karaniwan ay self), at ang target ay humahawak sa timer sa pamamagitan ng property. Kahit na ang timer ay isang beses lang gamitin, hindi ito mapapalaya hanggang sa invalidate. Solusyon: laging tawagan ang timer.invalidate() sa deinit o viewDidDisappear.

Mga layered na arkitektura

Sa mga arkitektura na may cascading ownership (mga coordinator, router) madalas lumilitaw ang multi-step na siklo: Coordinator → ViewController → ViewModel → Coordinator (sa pamamagitan ng callback). Bawat strong reference sa kadena ay dapat sinasadyang piliin — isang weak reference sa anumang link ay pumuputol sa siklo.

Retain Cycle sa Swift closures

Ang mga closure sa Swift ay kumukuha ng panlabas na variable sa pamamagitan ng strong reference. Kung ang isang closure ay naka-imbak bilang property ng isang bagay (hal. completion handler) at kumukuha ng self, nabubuo ang retain cycle: self → closure → self.

Ito ang pinakakaraniwang pinagmumulan ng retain cycle sa modernong Swift development. Ito ay lumilitaw nang hindi hayagan — maaaring hindi mapansin ng developer ang pagkuha ng self sa closure, lalo na kapag gumagamit ng pinaikling syntax na walang hayagang self.

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()
        }

        // ✅ Pag-aayos: capture list na may weak self
        onComplete = { [weak self] data in
            guard let self else { return }
            self.result = data
            self.notifyUI()
        }
    }

    func notifyUI() { }
}

Ang capture list [weak self] ay lumilikha ng mahinang reference sa self sa loob ng closure. Kung ang DownloadService ay napalaya bago isagawa ang closure, ang self ay nagiging nil at ang code ay ligtas na lumabas sa pamamagitan ng guard. Ito ang karaniwang pattern para sa asynchronous na mga closure sa Swift — dapat itong palaging ilapat kapag ang closure ay naka-imbak bilang property.

Unowned self sa mga closure

unowned self — alternatibo sa weak self, kapag ang self ay garantisadong mas mahaba ang buhay kaysa sa closure. Halimbawa: synchronous closure na agad na isinasagawa (sorted, filter). Sa ganitong mga kaso, ang self ay tiyak na buhay at ang unowned ay ligtas. Gayunpaman, ang unowned ay nagca-crash kapag ina-access ang napalaya nang bagay — kaya ang weak ay itinuturing na ligtas na pagpipilian bilang default.

Paano makita ang retain cycle: mga diagnostic tool

Ang maagang pagtuklas ng retain cycle ay mahalaga para sa pagganap ng aplikasyon. Tingnan natin ang mga pangunahing tool at pamamaraan para sa pagtukoy ng mga siklikong sanggunian sa iOS development.

Xcode Memory Debugger

Xcode Memory Debugger (Debug Memory Graph) — isang visual na tool na nagpapakita ng graf ng mga bagay sa memorya kasama ang kanilang mga sanggunian. Ang retain cycle ay ipinapakita bilang isang saradong kadena ng mga strong arrow. Upang patakbuhin: pindutin ang Debug Memory Graph button sa Debug area panel habang tumatakbo ang aplikasyon. Bawat bagay ay ipinapakita na may uri, address at listahan ng mga sanggunian.

Instruments Leaks

Instruments Leaks — profiler para sa awtomatikong pagtuklas ng mga leak. Nagre-record ng mga alokasyon at nag-aanalisa ng graf ng sanggunian sa real-time. Natutukoy hindi lamang ang retain cycle, kundi pati na rin ang mga nakalimutang sanggunian, hindi napalayang ViewController at iba pang leak. Ang Leaks ay nagpapahiwatig ng eksaktong bagay at kadena ng pagpapanatili.

Pag-log ng deinit

Pinakasimpleng paraan — magdagdag ng print sa deinit ng bawat pangunahing klase. Kung ang deinit ay hindi tinawag sa inaasahang pagkasira ng bagay — may retain cycle. Ang pamamaraang ito ay hindi nangangailangan ng mga tool at epektibo para sa paunang diagnostiko.

ToolUriKailan gagamitin
Memory DebuggerVisual na grafManu-manong pagsusuri pagkatapos ng nabigasyon
Instruments LeaksAwtomatikong analisisPagsubok ng regression, CI
deinit printManu-manong pag-logPag-develop, code review
Malloc ScribbleRuntime flagDebugg ng use-after-free

Inirerekomendang paraan: gamitin ang deinit logging sa yugto ng pag-develop, Memory Debugger — sa manu-manong pagsubok, Instruments Leaks — sa CI/CD pipeline para sa awtomatikong kontrol ng regression ng mga leak.

Pag-iwas sa retain cycle at best practices

Ang pag-iwas sa retain cycle ay mas madali kaysa sa pag-aayos nito sa produksyon. Ilang mga patakaran na nagpapababa ng panganib ng mga siklikong sanggunian sa pinakamababa.

Ang patakaran ng weak delegate

Lahat ng delegado at dataSource ay dapat weak. Ang patakarang ito ay naka-embed sa UIKit: lahat ng protokol ng delegado sa Apple SDK ay idineklara na may weak property (UITableView.delegate, UICollectionView.dataSource). Para sa iyong sariling protokol, gamitin ang weak var delegate: MyDelegate? at paganahin ang protokol mula sa AnyObject.

Capture list sa mga closure

Bawat closure na naka-imbak bilang property (completion handler, callback) at kumukuha ng self ay dapat gumamit ng [weak self] sa capture list. Pagbubukod — mga closure na agad na isinasagawa at hindi iniimbak (sorted, map, filter). Para sa kanila, ang unowned self ay ligtas.

Pagsusuri ng arkitektura

Sa mga kumplikadong arkitektura (VIPER, Coordinators, Redux) subaybayan ang direksyon ng strong reference. Ang may-ari ay nagtataglay ng strong reference sa subordinate, ngunit ang subordinate ay dapat sumangguni sa may-ari lamang sa pamamagitan ng weak o unowned. Ang unidirectional na daloy ng data ay nagpapasimple sa kontrol ng sanggunian.

swift
// Halimbawa: pagsusuri na may pag-log ng deinit
class BaseViewController: UIViewController {
    deinit {
        print("✅ \(type(of: self)) deallocated")
    }
}

// Paggamit: lahat ng ViewController ay nagmamana ng BaseViewController
class ProfileVC: BaseViewController {
    var viewModel: ProfileViewModel?
    var onLogout: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onLogout = { [weak self] in
            self?.dismiss(animated: true)
        }
    }
}
// Sa pagsara ng ProfileVC inaasahan ang „✅ ProfileVC deallocated” sa console

Ang base na klase na may logging ng deinit ay nagbibigay ng agarang feedback. Kung ang mensahe ay hindi lumitaw sa inaasahang pagsara ng screen — sa klase na ito ay may retain cycle. Idagdag ang kasanayang ito sa template ng proyekto para sa lahat ng ViewController.

Mga Madalas Itanong

Paano naiiba ang retain cycle sa leak ng memorya sa GC?

Retain cycle — isang partikular na problema ng ARC kung saan ang saradong bilog ng strong reference ay humaharang sa pagpapalaya. Sa GC, sinusuri ng kolektor ang abot mula sa root set, hindi ang counter ng sanggunian — kaya ang mga siklo ay hindi leak. Sa ARC, ang anumang nakahiwalay na siklo ay garantisadong leak.

Paano pinuputol ng weak reference ang retain cycle?

Ang Weak reference ay hindi nagpapataas ng retain count ng bagay. Kung papalitan mo ang isa sa mga strong reference sa siklo ng weak, ang retain count ng bawat bagay ay maaaring maging zero. Pagkapalaya ng bagay, ang weak reference ay awtomatikong nagiging nil, na pumipigil sa pag-access sa patay na memorya.

Maaari bang binubuo ang retain cycle ng tatlo o higit pang bagay?

Oo, ang retain cycle ay maaaring magsama ng anumang bilang ng mga bagay: A → B → C → A. Upang palayain, sapat na upang putulin ang isang link sa siklo — palitan ang anumang strong reference ng weak o unowned. Ipinapakita ng mga tool ang buong graf, hindi lamang mga pares ng bagay.

Bakit hindi lumilikha ng retain cycle ang GCD DispatchWorkItem?

GCD (Grand Central Dispatch) ay hindi nag-iimbak ng closure pagkatapos ng pagpapatupad. Ang DispatchWorkItem ay isinasagawa at pinapalaya, kahit na ang closure ay kumukuha ng self. Ang retain cycle ay lumilitaw lamang kapag ang closure ay naka-imbak bilang property (completion handler sa isang klase), hindi kapag ito ay ipinadala sa isang queue.

Anong mga uri ng retain cycle ang hindi natutukoy ng Instruments?

Instruments Leaks ay hindi palaging nakakahanap ng pansamantalang retain cycle (na umiiral nang ilang segundo) at mga siklikong sanggunian sa C/C++ na bagay sa pamamagitan ng bridge. Para sa kumpletong pagsusuri, gamitin ang Memory Debugger nang manu-mano + pag-log ng deinit ng lahat ng pangunahing bagay sa scene.

Buod

  • Retain Cycle — saradong kadena ng strong reference na humaharang sa pagpapalaya ng mga bagay sa ARC
  • Mga Sanhi — mga delegado na may strong reference, closures na kumukuha ng self, dalawang-daanang relasyong parent-child
  • Solusyon — palitan ang isang strong reference ng weak o unowned ay pumuputol sa siklo
  • Closures — ang mga naka-imbak na completion handler ay dapat laging gumamit ng [weak self]
  • Delegado — laging weak; ang protokol ng delegado ay dapat magmana ng AnyObject
  • Diagnostiko — Xcode Memory Debugger, Instruments Leaks, pag-log ng deinit
  • Pag-iwas — unidirectional na daloy ng data, weak delegate, capture list, base na klase na may deinit

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din