Strong Reference (malakas na sanggunian) — ay isang pamantayang mekanismo ng pamamahala ng memorya kung saan ang isang bagay ay nananatili sa memorya hangga't mayroong kahit isang aktibong sanggunian na tumuturo dito. Hindi tulad ng mahihinang sanggunian, ang malakas na sanggunian ay nagpapataas ng bilang ng sanggunian ng bagay at pumipigil sa awtomatikong pagpapalaya nito. Ayon sa Apple Developer Documentation, awtomatikong pinamamahalaan ng ARC ang habang-buhay ng mga bagay sa Swift at Objective-C. Ang pag-unawa sa paggana ng malalakas na sanggunian ay mahalaga para maiwasan ang pagtagas ng memorya at mga siklikal na dependency sa mga mobile application.
Mga pangunahing punto
Strong Reference — ay isang uri ng sanggunian sa isang bagay na pumipigil sa pagkawasak nito ng tagakolekta ng basura o sistema ng pamamahala ng memorya. Hangga't mayroong kahit isang malakas na sanggunian sa bagay, hindi nabubuklod ang memorya sa ilalim nito. Ito ang pangunahing mekanismo kung saan itinayo ang ARC sa Swift at Objective-C at ang pagkolekta ng basura sa Java at Kotlin.
Ang konsepto ng malakas na sanggunian ay pundamental para sa lahat ng mga wika na may awtomatikong pamamahala ng memorya. Sa mga sistemang may ARC, bawat malakas na sanggunian ay nagpapataas ng bilang ng sanggunian ng bagay. Kapag ang bilang ay bumaba sa zero, ang bagay ay agad na dealokasyon. Sa Java at Kotlin na may tagakolekta ng basura, ginagarantiya ng malakas na sanggunian na ang bagay ay maaabot at hindi kokolektahin ng GC.
Ayon sa datos ng WWDC 2021, humigit-kumulang 35% ng pagtagas ng memorya sa mga iOS application ay nauugnay sa hindi tamang paggamit ng malalakas na sanggunian at mga siklo ng pagpapanatili. Sa pagpapaunlad ng Android, ang pagtagas sa pamamagitan ng implicit strong reference sa mga closure at callback ay ang pangalawang pinakakaraniwang sanhi ng mga problema sa memorya pagkatapos ng Context Leak.
Para sa mabisang pagtatrabaho sa memorya, kinakailangang maunawaan ang pagkakaiba sa pagitan ng strong, weak at unowned na mga sanggunian at piliin nang tama ang uri ng sanggunian batay sa pagmamay-ari at habang-buhay ng mga bagay.
Bago ang pagpapakilala ng ARC, manu-manong tinatawag ng mga developer ang retain at release para sa bawat bagay, na humantong sa maraming error. Ang ARC, na ipinakilala ng Apple noong 2011 sa paglabas ng LLVM 3.0, ay awtomatiko ang prosesong ito sa pamamagitan ng pagsusuri ng graph ng pagmamay-ari sa yugto ng kompilasyon. Ang compiler mismo ang naglalagay ng mga tawag na retain, release at autorelease sa mga tamang lugar.
Ayon sa Clang Static Analyzer, ang pagpapakilala ng ARC ay nagbawas ng bilang ng mga bug na nauugnay sa memorya sa mga iOS application ng 70%. Para sa developer, nangangahulugan ito na ang pamamahala ng memorya ay naging mas ligtas, ngunit kasabay nito ay lumitaw ang pangangailangan na maunawaan kung paano gumagana ang malalakas na sanggunian sa ilalim ng hood — upang maiwasan ang retain cycles.
Sa Kotlin at Java, ang papel ng ARC ay ginagampanan ng tagakolekta ng basura, ngunit ang prinsipyo ng malakas na sanggunian ay nananatiling pareho: GC Roots — ito ang mga entry point kung saan ang mga bagay ay pinananatili ng malalakas na sanggunian. Hangga't ang isang bagay ay maaabot sa pamamagitan ng isang chain ng malalakas na sanggunian mula sa GC Root, hindi ito kokolektahin.
ARC (Automatic Reference Counting) ay gumagana sa prinsipyo ng pagbibilang ng mga sanggunian para sa bawat bagay sa heap. Kapag ang isang bagong malakas na sanggunian sa bagay ay nilikha, ang bilang ay tumataas (retain). Kapag ang sanggunian ay nawasak o na-overwrite, ang bilang ay bumababa (release). Kapag ang bilang ay umabot sa zero, ang bagay ay agad na tinatanggal mula sa memorya.
Isaalang-alang natin ang isang halimbawa sa Swift. Sa paglikha ng isang instance ng klase, ang ARC ay naglalaan ng memorya at nagtatakda ng retain count sa 1. Bawat bagong pagtatalaga sa ibang variable ay nagpapataas ng bilang. Kapag ang variable ay lumabas sa saklaw, ang bilang ay bumababa:
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// retain count = 1 para sa bagong instance
let user = User(name: "Ivan")
// retain count = 2 pagkatapos ng pagtatalaga ng nameLabel
nameLabel = user.name
// paglabas ng method — user ay lumabas ng scope, retain count = 1
}
}
Sa kodigong ito, ginagarantiya ng ARC na ang bagay na User ay nananatili sa memorya hangga't mayroong kahit isang malakas na sanggunian dito. Kapag natapos ang function na loadProfile, ang lokal na variable na user ay nawasak, ngunit hawak pa rin ng nameLabel ang bagay. Ang memorya ay pakakawalan lamang kapag ang nameLabel ay hindi na umiiral o na-overwrite.
Sa Kotlin, ang katulad na pag-uugali ay ibinibigay sa pamamagitan ng GC Roots. Hangga't mayroong isang masusubaybayang chain ng strong references mula sa ugat ng tagakolekta ng basura (halimbawa, isang static field o aktibong thread), ang bagay ay nananatili sa memorya. Ang pagkakaiba ay ang GC ay hindi nagpapalaya ng memorya kaagad — ito ay nangyayari nang asinkrono pagkatapos ng pagsusuri ng abot.
Sa ARC, ang pagpapalaya ay nangyayari nang sabay-sabay sa sandali ng pag-zero ng bilang. Sa Swift at Objective-C, alam mo nang eksakto kung kailan tatanggalin ang bagay. Sa Kotlin at Java, ang sandali ng pagpapalaya ay hindi mahuhulaan, ngunit ito ay binabayaran ng mas nababagong iskema ng pagtuklas ng mga siklikal na dependency sa antas ng tagakolekta ng basura.
Retain cycle (siklo ng pagpapanatili) — sitwasyon kung saan ang dalawa o higit pang mga bagay ay may magkabilang malalakas na sanggunian sa isa't isa. Bilang resulta, ang kanilang retain count ay hindi bumababa sa zero, at ang memorya ay hindi nabubuklod kahit na pagkatapos na ang mga bagay ay hindi na kailangan ng application.
Klasikong halimbawa: ang parent view controller ay humahawak ng child object na may malakas na sanggunian, at ito naman ay humahawak ng parent na may malakas na sanggunian. Ito ay tipikal para sa mga sitwasyon na may mga delegado, closure at nested lambda expressions. Ayon sa Instruments Leaks, ang retain cycles ay bumubuo ng hanggang 60% ng lahat ng pagtagas ng memorya sa mga application na gumagamit ng ARC.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: parent ay humahawak ng child, child ay humahawak ng parent sa pamamagitan ng closure
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
Ang problema dito ay ang closure na onEvent ay kumukuha ng self (ParentViewController) na may malakas na sanggunian, at ang ParentViewController mismo ay humahawak ng child na may malakas na sanggunian. Ang parehong bagay ay hindi kailanman pakakawalan. Ang solusyon — gamitin ang weak self sa closure upang maputol ang siklo.
Sa Kotlin, ang mga katulad na siklo ay lumilitaw kapag gumagamit ng mga lambda na kumukuha ng mga panlabas na bagay. Ang JVM tagakolekta ng basura ay maaaring makakita ng mga ganitong siklo sa paglipas ng panahon, ngunit kung ang mga bagay ay hindi maaabot mula sa GC Roots. Kung ang siklo ay nauugnay sa isang aktibong thread o UI context, ang pagtagas ay nananatili sa buong habang-buhay ng application.
Ang pag-unawa sa pagkakaiba sa pagitan ng mga uri ng sanggunian — susi sa ligtas na pamamahala ng memorya. Strong Reference ay nagpapataas ng retain count. Weak Reference ay hindi nagpapataas ng retain count at awtomatikong nagiging nil kapag nabuklod ang bagay. Unowned Reference ay hindi rin nagpapataas ng retain count, ngunit hindi nagze-zero — ang pagtukoy dito pagkatapos ng pagpapalaya ay nagdudulot ng crash.
| Uri ng sanggunian | Retain count | Kaligtasan | Kailan gagamitin |
|---|---|---|---|
| Strong | +1 | Ligtas (default) | Pagmamay-ari ng bagay, relasyong parent → child |
| Weak | Hindi nagbabago | Awtomatikong pag-zero (safe) | Mga delegado, callback, pabalik na sanggunian |
| Unowned | Hindi nagbabago | Peligro ng crash sa huling pagtukoy | Kapag ang bagay ay garantisadong nabubuhay nang mas mahaba kaysa sa may-ari |
Ang pagpili ng uri ng sanggunian ay idinidikta ng relasyon ng pagmamay-ari. Kung ang bagay B ay bahagi ng A at hindi maaaring umiral nang wala ito — gamitin ang Strong. Kung ang B ay maaaring umiral nang nakapag-iisa at tumutukoy sa A para sa mga abiso — gamitin ang Weak. Ang Unowned ay bihirang ginagamit — kung ang habang-buhay ng child object ay mahigpit na hindi lalampas sa habang-buhay ng parent.
Apple Developer Documentation ay nagrerekomenda: bilang default, gamitin ang strong para sa lahat ng relasyon ng pagmamay-ari. Kung kailangan mong iwasan ang retain cycle — tukuyin kung aling sanggunian ang dapat mahina. Karaniwan ito ang pabalik na sanggunian sa hierarchy (child → parent). Sa Kotlin, ang katulad na papel ay ginagampanan ng WeakReference mula sa java.lang.ref, na ginagamit para sa mga cache at pattern ng observer.
Ang pagtuklas ng retain cycles — unang hakbang. Ang pangalawa — ang tamang pag-aalis ng mga ito. Ang pangunahing kasangkapan sa paglaban sa mga siklo ng malalakas na sanggunian ay ang pagpapalit ng isa sa mga sanggunian ng weak o unowned. Sa mga wikang may pagkolekta ng basura, karagdagan na inilalapat ang WeakReference na may manu-manong pagsusuri ng null bago ang bawat pag-access.
Sa Swift at Objective-C, ang pinakakaraniwang pag-aayos ay ang pagdaragdag ng [weak self] sa mga closure. Ginagarantiya nito na ang closure ay hindi humahawak sa bagay pagkatapos nitong mapakawalan. Sa Kotlin, para sa mga katulad na layunin ay ginagamit ang WeakReference wrapper o malinaw na paglilinis ng sanggunian sa onDestroy.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// pagkuha sa pamamagitan ng weak self — retain cycle ay hindi kasama
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
Sa halimbawang ito, ginagarantiya ng [weak self] na ang NetworkService ay hindi hahawakan ng closure pagkatapos na hindi na ito kailangan. Kung ang self ay napakawalan bago matapos ang request — guard let self else { return } ay lumalabas sa closure nang hindi tinatawagan ang completion.
Para sa diagnosis ng retain cycles, gamitin ang Instruments Leaks para sa iOS o Android Profiler + LeakCanary para sa Android. Ang mga tool na ito ay nagpapakita ng eksaktong graph ng pagpapanatili at nagpapahiwatig kung aling malakas na sanggunian ang pumipigil sa pagpapalaya ng bagay. Ang regular na profiling ng memorya ay dapat maging bahagi ng CI/CD pipeline ng bawat mobile project.
Swift at Kotlin ay gumagamit ng fundamental na magkakaibang mekanismo ng pamamahala ng memorya, ngunit ang konsepto ng malakas na sanggunian ay naroroon sa pareho. Sa Swift, inilalapat ang ARC na may sabay-sabay na pagpapalaya sa retain count = 0. Sa Kotlin, ginagamit ang tracing GC na naglilinis nang asinkrono ng mga bagay na hindi maaabot.
| Parameter | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| Mekanismo | Pagbibilang ng sanggunian (retain count) | Pagsubaybay ng abot (GC Roots) |
| Pagpapalaya | Sabay-sabay (sa pag-zero ng bilang) | Asinkrono (sa pamamagitan ng GC cycle) |
| Retain cycle | Hindi awtomatikong natutukoy | Maaaring matukoy ng GC, ngunit hindi kaagad |
| Weak ref | weak (awtomatikong pag-zero) | WeakReference (manu-manong pagsusuri) |
Ang pangunahing praktikal na pagkakaiba: sa Swift, ang retain cycle — ay garantisadong pagtagas. Sa Kotlin, maaaring putulin ng GC ang siklo kung ang mga bagay ay hindi maaabot mula sa ugat, ngunit ang habang-buhay ng mga tumagas na bagay ay nananatiling hindi mahuhulaan. Samakatuwid, sa parehong wika, ang pinakamahusay na diskarte ay iwasan ang mga siklo ng malalakas na sanggunian sa yugto ng disenyo.
Para sa Swift, gamitin ang weak sa mga pattern ng delegado at closure. Para sa Kotlin — WeakReference o Lifecycle-aware na mga component na awtomatikong naglilinis ng mga sanggunian sa pagkawasak ng may-ari. Sa parehong diskarte, ang layunin ay pareho — alisin ang malalakas na sanggunian kung saan lumilikha sila ng hindi maputol na chain ng pagpapanatili.
Mga madalas itanong
Strong Reference ay nagpapataas ng retain count ng bagay at pumipigil sa pagpapalaya nito hangga't umiiral ang sanggunian. Ang Weak Reference ay hindi nagbabago ng retain count at awtomatikong nagze-zero kapag ang bagay ay tinanggal mula sa memorya. Ang malalakas na sanggunian ay ginagamit para sa pagmamay-ari, ang mahihinang sanggunian — para sa mga pabalik na koneksyon at delegado.
Retain cycle — isang magkabilang pagharang kung saan ang dalawang bagay ay humahawak sa isa't isa ng malalakas na sanggunian. Ang kanilang retain count ay hindi bumababa sa zero, ang memorya ay hindi nabubuklod. Ito ay humahantong sa pagtagas ng memorya: ang mga bagay ay nananatili sa heap magpakailanman, ang application ay kumokonsumo ng parami nang paraming mapagkukunan at sa kalaunan ay nag-crash sa OutOfMemory.
Gamitin ang Instruments Leaks mula sa Xcode — simulan ang profiling gamit ang Leaks template, isagawa ang scenario sa application at suriin ang mga indikator ng pagtagas. Para sa tumpak na diagnosis, lumipat sa tab na Cycles & Roots — ipapakita nito ang graph ng magkabilang strong reference na bumubuo ng hindi maputol na siklo.
Unowned ay ilapat kapag ang habang-buhay ng child object ay tiyak na hindi lalampas sa habang-buhay ng parent — halimbawa, kapag nagbubuklod ng isang bagay sa isang mahigpit na tinukoy na saklaw. Kung may pag-aalinlangan, gamitin ang Weak, dahil ang pagtukoy sa napakawalan nang unowned ay nagdudulot ng crash ng application.
Hindi direkta — oo. Ang bawat retain at release sa ARC ay isang atomikong operasyon na may overhead. Sa malaking bilang ng mga bagay sa mga siklo, ito ay maaaring makaapekto sa pagganap. Gayunpaman, ang pangunahing problema ay hindi ang bilis ng ARC, ngunit ang pagtagas ng memorya dahil sa maling napiling uri ng sanggunian.
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