Retain Cycle — özü, ortaya çıkış nedenleri ve uygulama geliştirmede giderilmesi

Yazar: IT Sectr Yayınlanma: 2026-03-29 Okuma süresi: 8 dk

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 — ARC’nin nesneleri serbest bırakamadığı kapalı bir güçlü referans zinciri
  • Neden — iki (veya daha fazla) nesne birbirine güçlü referans tutar, retain count’u sıfırlama imkansız
  • Sonuç — bellek sızıntısı: nesneler sonsuza kadar bellekte kalır, RAM tüketimi artar
  • Çözüm — döngüdeki güçlü referanslardan birini weak veya unowned ile değiştirme
  • Teşhis — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Retain Cycle Nedir?

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.

iOS geliştirmede retain cycle örnekleri

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.

Temsilci ile Ebeveyn-Çocuk

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.

swift
// 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 ve retain cycle

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.

Katmanlı mimariler

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 Closure’larında Retain Cycle

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.

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

        // ✅ 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.

Closure’larda Unowned self

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 nasıl tespit edilir: tanı araçları

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

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

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.

Deinit günlüğü

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ürNe zaman kullanılır
Memory DebuggerGörsel grafikGezinme sonrası manuel kontrol
Instruments LeaksOtomatik analizRegresyon testi, CI
deinit printManuel günlükGeliş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 önleme ve en iyi uygulamalar

Retain cycle’ları önlemek, onları üretimde düzeltmekten daha kolaydır. İşte döngüsel referans riskini en aza indiren birkaç kural.

Weak temsilci 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.

Closure’larda yakalama listesi

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.

Mimari incelemesi

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.

swift
// Ö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, GC’deki bellek sızıntısından nasıl farklıdır?

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 referans bir retain cycle’ı nasıl kırar?

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.

Bir retain cycle üç veya daha fazla nesneden oluşabilir mi?

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 DispatchWorkItem neden retain cycle oluşturmaz?

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.

Hangi tür retain cycle’lar Instruments tarafından tespit edilmez?

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

  • Retain Cycle — ARC’de nesne serbest bırakmayı engelleyen kapalı bir güçlü referans zinciri
  • Nedenler — güçlü referanslı temsilciler, self’i yakalayan closure’lar, çift yönlü ebeveyn-çocuk ilişkileri
  • Çözüm — bir güçlü referansı weak veya unowned ile değiştirmek döngüyü kırar
  • Closure’lar — saklanan completion handler’lar her zaman [weak self] kullanmalıdır
  • Temsilciler — her zaman weak; temsilci protokolü AnyObject’ten miras almalıdır
  • Tespit — Xcode Memory Debugger, Instruments Leaks, deinit günlüğü
  • Önleme — tek yönlü veri akışı, weak temsilciler, yakalama listeleri, deinit ile temel sınıf

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.

Projeyi tartış

Ayrıca okuyun