Strong Reference (güçlü referans), bir nesnenin en az bir aktif referans tarafından gösterildiği sürece bellekte kaldığı standart bir bellek yönetimi mekanizmasıdır. Zayıf referansların aksine, güçlü bir referans nesnenin referans sayacını artırır ve otomatik olarak serbest bırakılmasını engeller. Apple Developer Documentation'a göre ARC, Swift ve Objective-C'de nesnelerin yaşam döngüsünü otomatik olarak yönetir. Mobil uygulamalarda bellek sızıntılarını ve döngüsel bağımlılıkları önlemek için güçlü referansların çalışmasını anlamak kritik öneme sahiptir.
Önemli noktalar
Strong Reference, bir nesnenin çöp toplayıcı veya bellek yönetim sistemi tarafından yok edilmesini önleyen bir referans türüdür. Nesneye en az bir güçlü referans olduğu sürece, belleği serbest bırakılmaz. Bu, Swift ve Objective-C'de ARC ile Java ve Kotlin'de çöp toplamanın temelini oluşturan mekanizmadır.
Güçlü referans kavramı, otomatik bellek yönetimine sahip tüm diller için temeldir. ARC'li sistemlerde, her güçlü referans nesnenin referans sayacını artırır. Sayaç sıfıra düştüğünde, nesne hemen serbest bırakılır. Java ve Kotlin'de çöp toplama ile güçlü bir referans, nesnenin erişilebilir olduğunu ve GC tarafından toplanmayacağını garanti eder.
WWDC 2021'e göre, iOS uygulamalarındaki bellek sızıntılarının yaklaşık %35'i, güçlü referansların yanlış kullanımı ve retain cycle'lar ile ilgilidir. Android geliştirmede, closure'lar ve callback'lerdeki örtük güçlü referanslar yoluyla sızıntılar, Context Leak'ten sonra en sık görülen ikinci bellek sorunu nedenidir.
Bellekle etkili bir şekilde çalışmak için strong, weak ve unowned referansları arasındaki farkı anlamanız ve sahiplik ve nesne yaşam süresine göre doğru referans türünü seçmeniz gerekir.
ARC'den önce geliştiriciler her nesne için manuel olarak retain ve release çağırmak zorundaydı ve bu da çok sayıda hataya yol açıyordu. Apple tarafından 2011 yılında LLVM 3.0 ile tanıtılan ARC, derleme zamanında sahiplik grafiğini analiz ederek bu süreci otomatikleştirdi. Derleyicinin kendisi, gerekli yerlerde retain, release ve autorelease çağrılarını ekler.
Clang Static Analyzer'a göre, ARC'nin tanıtılması iOS uygulamalarındaki bellek ile ilgili hataları %70 oranında azalttı. Geliştiriciler için bu, bellek yönetiminin daha güvenli hale geldiği, ancak aynı zamanda retain cycle'lardan kaçınmak için güçlü referansların perde arkasında nasıl çalıştığını anlama ihtiyacının doğduğu anlamına gelir.
Kotlin ve Java'da, çöp toplayıcı ARC'nin rolünü oynar, ancak güçlü referans ilkesi aynı kalır: GC Roots, nesnelerin güçlü referanslarla tutulduğu giriş noktalarıdır. Bir nesne, bir GC Root'tan güçlü referans zinciri aracılığıyla erişilebilir olduğu sürece, toplanmayacaktır.
ARC (Automatic Reference Counting), yığındaki her nesne için referansları sayarak çalışır. Bir nesneye yeni bir güçlü referans oluşturulduğunda, sayaç artar (retain). Referans yok edildiğinde veya üzerine yazıldığında, sayaç azalır (release). Sayaç sıfıra ulaştığında, nesne derhal bellekten kaldırılır.
Swift'te bir örnek düşünelim. Bir sınıf örneği oluşturulduğunda, ARC bellek ayırır ve retain count'u 1 olarak ayarlar. Başka bir değişkene her yeni atama sayacı artırır. Değişken kapsam dışına çıktığında, sayaç azalır:
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// yeni örnek için retain count = 1
let user = User(name: "Ivan")
// nameLabel atandıktan sonra retain count = 2
nameLabel = user.name
// metottan çıkış — user kapsam dışına çıkar, retain count = 1
}
}
Bu kodda ARC, User nesnesine en az bir güçlü referans olduğu sürece bellekte kalmasını sağlar. loadProfile işlevi sona erdiğinde, yerel user değişkeni yok edilir, ancak nameLabel hala nesneyi tutar. Bellek yalnızca nameLabel'in varlığı sona erdiğinde veya üzerine yazıldığında serbest bırakılır.
Kotlin'de benzer bir davranış GC Roots aracılığıyla sağlanır. Bir çöp toplayıcı kökünden (orneğin, statik bir alan veya aktif bir iş parçacığı) izlenebilir bir güçlü referans zinciri olduğu sürece, nesne bellekte kalır. Fark, GC'nin belleği anında serbest bırakmamasıdır — bu, erişilebilirlik analizinden sonra eşzamansız olarak gerçekleşir.
ARC'de, serbest bırakma sayaç sıfıra ulaştığında eşzamanlı olarak gerçekleşir. Swift ve Objective-C'de, nesnenin ne zaman kaldırılacağını tam olarak bilirsiniz. Kotlin ve Java'da, serbest bırakma anı öngörülemez, ancak bu, çöp toplayıcı düzeyinde döngüsel bağımlılıkları tespit etmek için daha esnek bir şema ile telafi edilir.
Retain cycle (tutma döngüsü) — iki veya daha fazla nesnenin birbirine karşılıklı güçlü referanslara sahip olduğu bir durumdur. Sonuç olarak, retain count'ları asla sıfıra düşmez ve nesneler uygulama için artık gerekli olmasa bile bellek asla serbest bırakılmaz.
Klasik bir örnek: bir üst görünüm denetleyicisi, güçlü bir referansla alt nesneyi tutar ve alt nesne de güçlü bir referansla üst nesneyi tutar. Bu, delegeler, closure'lar ve iç içe lambda ifadeleri olan durumlarda tipiktir. Instruments Leaks'e göre, retain cycle'lar ARC kullanan uygulamalardaki tüm bellek sızıntılarının %60'ına kadarını oluşturur.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: ebeveyn çocuğu tutar, çocuk closure aracılığıyla ebeveyni tutar
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
Buradaki sorun, onEvent closure'ının self'i (ParentViewController) güçlü bir referansla yakalaması ve ParentViewController'ın kendisinin child'ı güçlü bir referansla tutmasıdır. Her iki nesne de asla serbest bırakılmayacaktır. Çözüm, döngüyü kırmak için closure'da weak self kullanmaktır.
Kotlin'de, dış nesneleri yakalayan lambdalar kullanıldığında benzer döngüler ortaya çıkar. JVM çöp toplayıcısı, zaman içinde bu tür döngüleri tespit edebilir, ancak yalnızca nesneler GC Roots'tan erişilemezse. Döngü aktif bir iş parçacığına veya UI bağlamına bağlıysa, sızıntı uygulamanın tüm ömrü boyunca devam eder.
Referans türleri arasındaki farkı anlamak, güvenli bellek yönetiminin anahtarıdır. Strong Reference retain count'u artırır. Weak Reference retain count'u artırmaz ve nesne serbest bırakıldığında otomatik olarak nil olur. Unowned Reference da retain count'u artırmaz ancak sıfırlanmaz — serbest bırakıldıktan sonra ona erişmek çökmeye neden olur.
| Referans Türü | Retain count | Güvenlik | Ne Zaman Kullanılmalı |
|---|---|---|---|
| Strong | +1 | Güvenli (varsayılan) | Nesne sahipliği, ebeveyn → çocuk ilişkisi |
| Weak | Değiştirmez | Otomatik sıfırlama (güvenli) | Delegeler, callbacks, ters referanslar |
| Unowned | Değiştirmez | Geç erişimde çökme riski | Nesnenin sahibinden daha uzun yaşaması garanti edildiğinde |
Referans türü seçimi, sahiplik ilişkisi tarafından belirlenir. B nesnesi A'nın bir parçasıysa ve onsuz var olamıyorsa — Strong kullanın. B bağımsız olarak var olabiliyor ve bildirimler için A'ya referans veriyorsa — Weak kullanın. Unowned nadiren kullanılır — yalnızca alt nesnenin ömrü üst nesneninkini aşmadığında.
Apple Developer Documentation şunu önerir: varsayılan olarak, tüm sahiplik ilişkileri için strong kullanın. Bir retain cycle'dan kaçınmanız gerekiyorsa — hangi referansın zayıf olması gerektiğini belirleyin. Genellikle bu, hiyerarşideki ters referanstır (çocuk → ebeveyn). Kotlin'de, java.lang.ref'teki WeakReference benzer bir rol oynar ve önbellekler ve observer desenleri için kullanılır.
Retain cycle'ları tespit etmek ilk adımdır. İkincisi, onları doğru şekilde ortadan kaldırmaktır. Güçlü referans döngülerini kırmanın ana aracı, referanslardan birini weak veya unowned ile değiştirmektir. Çöp toplamalı dillerde, her erişimden önce manuel null denetimi ile WeakReference ek olarak kullanılır.
Swift ve Objective-C'de en yaygın düzeltme, closure'lara [weak self] eklemektir. Bu, closure'ın nesneyi serbest bırakıldıktan sonra tutmamasını sağlar. Kotlin'de, benzer amaçlar için WeakReference sarmalayıcıları veya onDestroy'de açık referans temizliği kullanılır.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// weak self ile yakalama — retain cycle ortadan kaldırıldı
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
Bu örnekte, [weak self] NetworkService'in artık gerekli olmadığında closure tarafından tutulmamasını sağlar. İstek tamamlanmadan önce self serbest bırakılırsa — guard let self else { return } completion'ı çağırmadan closure'dan çıkar.
Retain cycle teşhisi için iOS için Instruments Leaks veya Android için Android Profiler + LeakCanary kullanın. Bu araçlar tam tutma grafiğini gösterir ve hangi güçlü referansın nesnenin serbest bırakılmasını engellediğini belirtir. Düzenli bellek profili oluşturma, herhangi bir mobil projenin CI/CD boru hattının bir parçası olmalıdır.
Swift ve Kotlin temelde farklı bellek yönetim mekanizmaları kullanır, ancak güçlü referans kavramı her ikisinde de mevcuttur. Swift, retain count = 0'da eşzamanlı serbest bırakma ile ARC kullanır. Kotlin, erişilemeyen nesneleri eşzamansız olarak temizleyen bir izleme GC'si kullanır.
| Parametre | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| Mekanizma | Referans sayma (retain count) | Erişilebilirlik izleme (GC Roots) |
| Serbest bırakma | Eşzamanlı (sayaç sıfıra ulaştığında) | Eşzamansız (GC döngüsüyle) |
| Retain cycle | Otomatik olarak algılanmaz | GC algılayabilir, ancak hemen değil |
| Zayıf ref | weak (otomatik sıfırlama) | WeakReference (manuel denetim) |
Ana pratik fark: Swift'te retain cycle kesin bir sızıntıdır. Kotlin'de, nesneler kökten erişilemezse GC döngüyü kırabilir, ancak sızıntı yapan nesnelerin ömrü öngörülemez kalır. Bu nedenle, her iki dilde de en iyi strateji, tasarım aşamasında güçlü referans döngülerinden kaçınmaktır.
Swift için, temsilci desenlerinde ve closure'larda weak kullanın. Kotlin için, sahibi yok edildiğinde referansları otomatik olarak temizleyen WeakReference veya Lifecycle-aware bileşenlerini kullanın. Her iki yaklaşımda da amaç aynıdır — kırılmaz bir tutma zinciri oluşturdukları yerlerde güçlü referansları ortadan kaldırmak.
Sıkça Sorulan Sorular
Strong Reference nesnenin retain count'unu artırır ve referans var olduğu sürece serbest bırakılmasını engeller. Weak Reference retain count'u değiştirmez ve nesne bellekten kaldırıldığında otomatik olarak nil olur. Güçlü referanslar sahiplik için, zayıf referanslar ters bağlantılar ve delegeler için kullanılır.
Retain cycle — iki nesnenin birbirini güçlü referanslarla tuttuğu karşılıklı bir kilittir. Retain count'ları asla sıfıra düşmez, bellek serbest bırakılmaz. Bu, bellek sızıntılarına yol açar: nesneler sonsuza kadar yığında kalır, uygulama giderek daha fazla kaynak tüketir ve sonunda OutOfMemory ile çöker.
Xcode'dan Instruments Leaks kullanın — Leaks şablonuyla profilleme çalıştırın, uygulamada bir senaryo yürütün ve sızıntı göstergelerini kontrol edin. Kesin teşhis için Cycles & Roots sekmesine geçin — kırılmaz bir döngü oluşturan karşılıklı güçlü referansların grafiğini gösterir.
Unowned, alt nesnenin ömrünün üst nesneninkini aşmadığı garanti edildiğinde kullanılmalıdır — örneğin, bir nesneyi kesin olarak tanımlanmış bir kapsama bağlarken. Şüpheniz varsa, Weak kullanın, çünkü serbest bırakılmış bir unowned referansına erişmek uygulamanın çökmesine neden olur.
Dolaylı olarak — evet. ARC'deki her retain ve release ek yükü olan atomik bir işlemdir. Döngülerde çok sayıda nesne varsa, bu performansı etkileyebilir. Ancak ana sorun ARC'nin hızı değil, yanlış seçilmiş bir referans türü nedeniyle oluşan bellek sızıntılarıdır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun