Retain Cycle, ARC’de iki veya daha fazla nesnenin güçlü referanslar aracılığıyla birbirine referans vererek kapalı bir döngü oluşturduğu bir durumdur. Apple Memory Management Guide, 2026’ya göre, retain cycle döngüdeki tüm nesnelerin serbest bırakılmasını engeller çünkü her birinin retain count’u ≥ 1’dir. GC’deki bellek sızıntısının aksine, retain cycle, döngünün en az bir dış katılımcısı hayatta olduğu sürece nesnelerin canlı kalmasını garanti eder — ve döngü izole edilmişse, tüm dış referanslar kaybedildikten sonra bile.
Temel Çıkarımlar
Retain Cycle, iki veya daha fazla nesnenin güçlü referanslar aracılığıyla birbirine sahip olduğu ve kapalı bir bağımlılık grafiği oluşturduğu bir durumdur. ARC bu nesnelerin hiçbirini serbest bırakamaz çünkü her birinin retain count’u her zaman ≥ 1’dir: A nesnesi B’yi tutar, B A’yı tutar ve sayaçları asla sıfıra ulaşmaz.
Sorun yalnızca referans sayma sistemlerinde (ARC, MRR) ortaya çıkar. Çöp Toplama’da (GC), toplayıcı kök kümesinden referans grafiği aracılığıyla ulaşılamazlığı belirler — döngüler bir engel değildir. ARC’de ise bir döngü bir sızıntıya eşdeğerdir, çünkü belirleyici serbest bırakma sayma yoluyla dairesel bağımlılıkları çözemez.
WWDC 2012 Session 406’ya göre, retain cycle, Objective-C ve Swift uygulamalarında bellek sızıntılarının en yaygın nedenidir. Tipik senaryolar: temsilcilerle ebeveyn-çocuk ilişkileri, self’i yakalayan closure’lar ve çift yönlü ilişkilere sahip katmanlı mimariler.
Her iOS geliştiricisinin karşılaştığı klasik retain cycle senaryolarını inceleyelim. Bu kalıpları anlamak, ARC ile güvenli kod yazmanın temelidir.
Klasik senaryo: Bir ebeveyn nesnesi (örn. UIViewController) bir çocuk nesnesi oluşturur ve onun temsilcisi olur. Her ikisi de güçlü referans kullanırsa, bir retain cycle oluşur. Çözüm — temsilci weak olmalıdır.
// HATA: güçlü temsilci üzerinden retain cycle
protocol ChildDelegate: AnyObject { }
class ParentVC: UIViewController, ChildDelegate {
var child: ChildVC?
func showChild() {
child = ChildVC()
child?.delegate = self // Parent → Child (strong)
} // Child → Parent (delegate üzerinden strong)
} // ⚠️ Retain cycle!
class ChildVC: UIViewController {
var delegate: ChildDelegate? // ❌ varsayılan strong
}
// DÜZELTME: weak temsilci
class ChildVC: UIViewController {
weak var delegate: ChildDelegate? // ✅ weak — tutmaz
}
Örnekte, ParentVC child özelliği aracılığıyla ChildVC üzerinde güçlü bir referans tutar. ChildVC, delegate aracılığıyla ParentVC üzerinde güçlü bir referans tutar. Döngü kapalıdır. Düzeltme: weak var delegate — referans retain count’u artırmaz ve ParentVC serbest bırakılabilir.
NSTimer retain cycle’ın klasik bir kaynağıdır. Zamanlayıcı hedefini (genellikle self) tutar ve hedef, bir özellik aracılığıyla zamanlayıcıyı tutar. Zamanlayıcı tek kullanımlık olsa bile, invalidate çağrılana kadar serbest bırakılmaz. Çözüm: deinit veya viewDidDisappear içinde her zaman timer.invalidate() çağırın.
Basamaklı sahipliğe sahip mimarilerde (koordinatörler, yönlendiriciler), genellikle çok adımlı döngüler oluşur: Coordinator → ViewController → ViewModel → Coordinator (geri arama yoluyla). Zincirdeki her güçlü referans bilinçli olarak seçilmelidir — herhangi bir bağlantıdaki tek bir weak referans döngüyü kırar.
Swift’teki closure’lar, dış değişkenleri güçlü referansla yakalar. Bir closure bir nesnenin özelliği olarak saklanırsa (örn. bir completion handler) ve self’i yakalarsa, bir retain cycle oluşturur: self → closure → self.
Bu, modern Swift geliştirmede retain cycle’ın en yaygın kaynağıdır. Örtük olarak gerçekleşir — bir geliştirici, özellikle açık self olmadan kısaltma sözdizimi kullanırken, bir closure içinde self yakalamasını fark etmeyebilir.
class DownloadService {
var onComplete: ((Data) -> Void)?
var result: Data?
func startDownload() {
// ❌ Retain cycle: self → onComplete → self
onComplete = { data in
self.result = data
self.notifyUI()
}
// ✅ Düzeltme: weak self ile yakalama listesi
onComplete = { [weak self] data in
guard let self else { return }
self.result = data
self.notifyUI()
}
}
func notifyUI() { }
}
Bir yakalama listesi [weak self], closure içinde self’e zayıf bir referans oluşturur. Closure yürütülmeden önce DownloadService serbest bırakılırsa, self nil olur ve kod guard aracılığıyla güvenli bir şekilde çıkar. Bu, Swift’teki asenkron closure’lar için standart bir kalıptır — bir closure bir özellik olarak saklandığında her zaman kullanılmalıdır.
unowned self, self’in closure’dan daha uzun yaşayacağı garanti edildiğinde weak self’e bir alternatiftir. Örnek: hemen yürütülen senkron closure’lar (sorted, filter). Bu durumlarda self kesinlikle canlıdır ve unowned güvenlidir. Ancak unowned, serbest bırakılmış bir nesneye erişildiğinde çöker — bu nedenle weak, varsayılan güvenli seçenek olarak kabul edilir.
Retain cycle’ları erken bir aşamada tespit etmek, uygulama performansı için kritik öneme sahiptir. iOS geliştirmede döngüsel referansları belirlemek için ana araçları ve teknikleri gözden geçirelim.
Xcode Memory Debugger (Debug Memory Graph), bellekteki nesnelerin grafiğini referanslarıyla birlikte gösteren görsel bir araçtır. Bir retain cycle, güçlü okların kapalı bir zinciri olarak görünür. Başlatmak için: uygulama çalışırken Debug area panelindeki Debug Memory Graph düğmesine tıklayın. Her nesne, türü, adresi ve referans listesiyle birlikte gösterilir.
Instruments Leaks, otomatik sızıntı tespiti için bir profil oluşturucudur. Ayırmaları kaydeder ve referans grafiğini gerçek zamanlı olarak analiz eder. Yalnızca retain cycle’ları değil, aynı zamanda unutulan referansları, serbest bırakılmayan ViewController’ları ve diğer sızıntıları da tespit eder. Leaks, tam nesneyi ve tutma zincirini belirtir.
En basit yöntem, her ana sınıfın deinit’ine bir print eklemektir. Nesnenin yok edilmesi beklenirken deinit çağrılmazsa, bir retain cycle vardır. Bu yöntem araç gerektirmez ve ilk tanı için etkilidir.
| Araç | Tür | Ne zaman kullanılır |
|---|---|---|
| Memory Debugger | Görsel grafik | Gezinme sonrası manuel kontrol |
| Instruments Leaks | Otomatik analiz | Regresyon testi, CI |
| deinit print | Manuel günlük | Geliştirme, kod incelemesi |
| Malloc Scribble | Çalışma zamanı bayrağı | Use-after-free hata ayıklama |
Önerilen yaklaşım: geliştirme sırasında deinit günlüğü, manuel testlerde Memory Debugger ve otomatik regresyon sızıntı tespiti için CI/CD hattında Instruments Leaks kullanın.
Retain cycle’ları önlemek, onları üretimde düzeltmekten daha kolaydır. İşte döngüsel referans riskini en aza indiren birkaç kural.
Tüm temsilciler ve dataSource’lar weak olmalıdır. Bu kural UIKit’e yerleşik olarak gelir: Apple SDK’sindeki tüm temsilci protokolleri weak özelliklerle bildirilir (UITableView.delegate, UICollectionView.dataSource). Kendi protokolleriniz için weak var delegate: MyDelegate? kullanın ve protokolü AnyObject’ten miras alın.
Bir özellik olarak saklanan (completion handler, callback) ve self’i yakalayan her closure, yakalama listesinde [weak self] kullanmalıdır. İstisna, hemen yürütülen ve saklanmayan closure’lardır (sorted, map, filter). Bunlar için unowned self güvenlidir.
Karmaşık mimarilerde (VIPER, Coordinator’lar, Redux), güçlü referansların yönünü izleyin. Sahip, ast üzerinde güçlü bir referans tutar, ancak ast, sahibine yalnızca weak veya unowned aracılığıyla referans vermelidir. Tek yönlü veri akışı, referans yönetimini basitleştirir.
// Örnek: deinit günlüğüyle kontrol
class BaseViewController: UIViewController {
deinit {
print("✅ \(type(of: self)) deallocated")
}
}
// Kullanım: tüm ViewController’lar BaseViewController’dan miras alır
class ProfileVC: BaseViewController {
var viewModel: ProfileViewModel?
var onLogout: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onLogout = { [weak self] in
self?.dismiss(animated: true)
}
}
}
// ProfileVC kapatıldığında konsolda "✅ ProfileVC deallocated" bekleniyor
Deinit günlüğü olan bir temel sınıf anında geri bildirim sağlar. Ekran kapanması beklenirken mesaj görünmezse, bu sınıfta bir retain cycle vardır. Tüm ViewController’lar için bu uygulamayı proje şablonuna ekleyin.
Sıkça Sorulan Sorular
Retain cycle, güçlü referanslardan oluşan kapalı bir döngünün serbest bırakmayı engellediği belirli bir ARC sorunudur. GC’de toplayıcı, referans sayılarını değil, kök kümesinden erişilebilirliği analiz eder — bu nedenle döngüler sızıntı değildir. ARC’de ise, izole edilmiş herhangi bir döngü garantili bir sızıntıdır.
Weak bir referans, bir nesnenin retain count’unu artırmaz. Bir döngüdeki güçlü referanslardan birini weak ile değiştirirseniz, her nesnenin retain count’u sıfıra ulaşabilir. Nesne serbest bırakıldıktan sonra, weak referans otomatik olarak nil olarak ayarlanır ve serbest bırakılmış belleğe erişimi engeller.
Evet, bir retain cycle herhangi bir sayıda nesne içerebilir: A → B → C → A. Bunu kırmak için, döngüdeki tek bir halkayı kırmanız yeterlidir — herhangi bir güçlü referansı weak veya unowned ile değiştirin. Araçlar, yalnızca nesne çiftlerini değil, grafiğin tamamını gösterir.
GCD (Grand Central Dispatch), yürütmeden sonra closure’u saklamaz. DispatchWorkItem yürütülür ve serbest bırakılır, closure self’i yakalasa bile. Retain cycle yalnızca bir closure bir özellik (bir sınıftaki completion handler) olarak saklandığında oluşur, bir kuyruğa iletildiğinde değil.
Instruments Leaks geçici retain cycle’ları (saniyeler süren) veya köprüleme yoluyla C/C++ nesnelerindeki döngüsel referansları her zaman bulamaz. Tam bir kontrol için, sahnedeki tüm ana nesnelerin deinit günlüğüyle birlikte Memory Debugger’ı manuel olarak kullanın.
Ö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