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 — 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.
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.
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.
// 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 — 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.
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.
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.
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 — 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.
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 (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 — 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.
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.
| Tool | Uri | Kailan gagamitin |
|---|---|---|
| Memory Debugger | Visual na graf | Manu-manong pagsusuri pagkatapos ng nabigasyon |
| Instruments Leaks | Awtomatikong analisis | Pagsubok ng regression, CI |
| deinit print | Manu-manong pag-log | Pag-develop, code review |
| Malloc Scribble | Runtime flag | Debugg 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.
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.
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.
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.
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.
// 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
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.
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.
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.
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.
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
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.
Basahin din