Retain Cycle — mahiyyəti, yaranma səbəbləri və tətbiq inkişafında aradan qaldırılması

Müəllif: IT Sectr Dərc olunub: 2026-03-29 Oxuma vaxtı: 8 dəq

Retain Cycle (tsiklik istinad) — ARC-də iki və ya daha çox obyektin strong istinadlar vasitəsilə bir-birinə istinad edərək qapalı dövrə yaratması vəziyyətidir. Apple Memory Management Guide, 2026-ya görə, retain cycle dövrədəki bütün obyektlərin azad edilməsini bloklayır, çünki hər birinin retain count ≥ 1 olur. GC-dəki yaddaş sızmasından fərqli olaraq, retain cycle dövrün ən azı bir xarici iştirakçısı yaşadığı müddətcə obyektləri canlı saxlayır — və hətta bütün xarici istinadlar itirildikdən sonra da, dövrə təcrid olunmuşsa.

Əsas məqamlar

  • Retain Cycle — strong istinadların qapalı zənciri, obyektlərin ARC tərəfindən azad edilə bilməməsi
  • Səbəb — iki (və ya daha çox) obyekt bir-birinə strong istinad saxlayır, retain count sıfırlanması mümkün deyil
  • Nəticələr — yaddaş sızması: obyektlər əbədi olaraq yaddaşda qalır, RAM istehlakı artır
  • Həll — dövrədəki strong istinadlardan birinin weak və ya unowned ilə əvəz edilməsi
  • Diaqnostika — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Retain Cycle nədir?

Retain Cycle — iki və ya daha çox obyektin strong istinadlar vasitəsilə bir-birinə sahib olduğu, qapalı asılılıq qrafiki yaratdığı vəziyyətdir. ARC bu obyektlərdən heç birini azad edə bilməz, çünki hər birinin retain count həmişə ≥ 1 olur: A obyekti B-ni, B isə A-nı saxlayır və onların sayğacları heç vaxt sıfırlanmır.

Problem yalnız istinad sayma sistemlərində (ARC, MRR) yaranır. Garbage Collection-də toplayıcı kök dəstindən (root set) istinad qrafiki üzrə əlçatmazlığı müəyyən edir — dövrələr maneə deyil. ARC-də isə dövrə sızma ilə bərabərdir, çünki deterministik azadetmə sayğaca əsaslanaraq dairəvi asılılığı həll edə bilməz.

WWDC 2012 Session 406-ya görə, retain cycle Objective-C və Swift tətbiqlərində yaddaş sızmalarının ən geniş yayılmış səbəbidir. Tipik ssenarilər: deleqatlarla parent-child münasibətləri, self-i ələ keçirən bağlanmalar və ikitərəfli əlaqələri olan təbəqəli arxitekturalar.

iOS inkişafında retain cycle nümunələri

Hər bir iOS developerin qarşılaşdığı klassik retain cycle ssenarilərini nəzərdən keçirək. Bu nümunələri başa düşmək ARC ilə təhlükəsiz kod yazmağın əsasıdır.

Deleqatla Parent-Child

Klassik ssenari: valideyn obyekt (məsələn, UIViewController) uşaq obyekt yaradır və onun deleqatı olur. Əgər hər ikisi strong istinadlardan istifadə edərsə, retain cycle yaranır. Həll — deleqat weak olmalıdır.

swift
// XƏTA: strong delegate vasitəsilə 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 vasitəsilə strong)
}                                     // ⚠️ Retain cycle!

class ChildVC: UIViewController {
    var delegate: ChildDelegate?    // ❌ standart olaraq strong
}

// DÜZƏLİŞ: weak delegate
class ChildVC: UIViewController {
    weak var delegate: ChildDelegate? // ✅ weak — saxlamır
}

Nümunədə ParentVC child xassəsi vasitəsilə ChildVC-yə strong istinad saxlayır. ChildVC isə delegate vasitəsilə ParentVC-yə strong istinad saxlayır. Dövrə qapanır. Düzəliş: weak var delegate — istinad retain count-u artırmır və ParentVC azad edilə bilər.

NSTimer və retain cycle

NSTimer — klassik retain cycle mənbəyidir. Timer target-i (adətən self) saxlayır, target isə timer-i xassə vasitəsilə saxlayır. Timer birdəfəlik olsa belə, invalidate edilənə qədər azad olunmur. Həll: həmişə timer.invalidate() çağırın deinit və ya viewDidDisappear-da.

Təbəqəli arxitekturalar

Kaskad sahiblik arxitekturalarında (koordinatorlar, routerlər) çoxpilləli dövrələr tez-tez yaranır: Coordinator → ViewController → ViewModel → Coordinator (callback vasitəsilə). Zəncirdəki hər bir strong istinad şüurlu seçilməlidir — bir weak istinad istənilən halqada dövrəni qırar.

Swift bağlanmalarında Retain Cycle

Swift-də bağlanmalar (closures) xarici dəyişənləri strong istinadla ələ keçirir. Əgər bağlanma obyektin xassəsi kimi saxlanılırsa (məsələn, completion handler) və self-i ələ keçirirsə, retain cycle yaranır: self → closure → self.

Bu müasir Swift inkişafında ən geniş yayılmış retain cycle mənbəyidir. O, gizli şəkildə yaranır — developer bağlanmada self-in ələ keçirilməsini görməyə bilər, xüsusən aşkar self olmadan qısaldılmış sintaksisdən istifadə edərkən.

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üzəliş: weak self ilə capture list
        onComplete = { [weak self] data in
            guard let self else { return }
            self.result = data
            self.notifyUI()
        }
    }

    func notifyUI() { }
}

Capture list [weak self] bağlanma daxilində self-ə zəif istinad yaradır. DownloadService bağlanma icra olunmadan azad edilərsə, self nil olur və kod guard vasitəsilə təhlükəsiz şəkildə çıxır. Bu, Swift-də asinxron bağlanmalar üçün standart nümunədir — bağlanma xassə kimi saxlanıldıqda həmişə tətbiq edilməlidir.

Bağlanmalarda Unowned self

unowned self — weak self-ə alternativdir, self bağlanmadan daha uzun yaşayacağı təmin edildikdə istifadə olunur. Nümunə: dərhal icra olunan sinxron bağlanma (sorted, filter). Belə hallarda self mütləq canlıdır və unowned təhlükəsizdir. Lakin unowned azad edilmiş obyektə müraciət etdikdə crash edir — buna görə weak təhlükəsiz seçim sayılır.

Retain cycle necə aşkar edilir: diaqnostika alətləri

Retain cycle-in erkən mərhələdə aşkarlanması tətbiqin performansı üçün kritik əhəmiyyət daşıyır. iOS inkişafında tsiklik istinadları müəyyən etmək üçün əsas alətlər və metodları nəzərdən keçirək.

Xcode Memory Debugger

Xcode Memory Debugger (Debug Memory Graph) — yaddaşdakı obyektlərin qrafikini istinadları ilə göstərən vizual alətdir. Retain cycle strong oxlarının qapalı zənciri kimi göstərilir. İşə salmaq üçün: tətbiq işləyərkən Debug area panelində Debug Memory Graph düyməsini klikləyin. Hər bir obyekt tipi, ünvanı və istinad siyahısı ilə göstərilir.

Instruments Leaks

Instruments Leaks — sızmaların avtomatik aşkarlanması üçün profilerdir. Allokasiyaları qeyd edir və istinad qrafikini real vaxtda analiz edir. Təkcə retain cycle deyil, həm də unudulmuş istinadları, azad edilməmiş ViewController və digər sızmaları aşkarlayır. Leaks dəqiq obyekti və saxlama zəncirini göstərir.

Deinit loglaması

Ən sadə üsul — hər bir əsas sinfin deinit-inə print əlavə edin. Əgər obyektin gözlənilən məhvində deinit çağırılmırsa — retain cycle var. Bu üsul alətlər tələb etmir və ilkin diaqnostika üçün effektivdir.

AlətTipNə vaxt tətbiq edilməli
Memory DebuggerVizual qrafikNaviqasiyadan sonra əl ilə yoxlama
Instruments LeaksAvtomatik analizReqresiya testi, CI
deinit printƏl ilə loglamaİnkişaf, code review
Malloc ScribbleRuntime bayrağıUse-after-free debug

Tövsiyə olunan yanaşma: inkişaf mərhələsində deinit loglaması, əl ilə test zamanı Memory Debugger, CI/CD pipeline-da avtomatik reqresiya sızma nəzarəti üçün Instruments Leaks istifadə edin.

Retain cycle profilaktikası və best practices

Retain cycle-in qarşısını almaq onu prodda düzəltməkdən asandır. Tsiklik istinadların riskini minimuma endirən bir neçə qayda.

Weak delegate qaydası

Bütün deleqatlar və dataSource weak olmalıdır. Bu qayda UIKit-ə daxil edilmişdir: Apple SDK-dakı bütün deleqat protokolları weak xassələrlə elan edilmişdir (UITableView.delegate, UICollectionView.dataSource). Öz protokollarınız üçün weak var delegate: MyDelegate? istifadə edin və protokolu AnyObject-dən törədin.

Bağlanmalarda capture list

Xassə kimi saxlanılan (completion handler, callback) və self-i ələ keçirən hər bir bağlanma capture list-də [weak self] istifadə etməlidir. İstisna — dərhal icra olunan və saxlanılmayan bağlanmalar (sorted, map, filter). Onlar üçün unowned self təhlükəsizdir.

Arxitekturanın yoxlanması

Mürəkkəb arxitekturalarda (VIPER, Coordinators, Redux) strong istinadların istiqamətini izləyin. Sahib podçinə strong istinad saxlayır, lakin podçin sahibə yalnız weak və ya unowned vasitəsilə istinad etməlidir. Biristiqamətli məlumat axını (unidirectional data flow) istinadlara nəzarəti asanlaşdırır.

swift
// Nümunə: deinit loglaması ilə yoxlama
class BaseViewController: UIViewController {
    deinit {
        print("✅ \(type(of: self)) deallocated")
    }
}

// İstifadə: bütün ViewController BaseViewController-dən törəyir
class ProfileVC: BaseViewController {
    var viewModel: ProfileViewModel?
    var onLogout: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()
        onLogout = { [weak self] in
            self?.dismiss(animated: true)
        }
    }
}
// ProfileVC bağlandıqda konsolda "✅ ProfileVC deallocated" gözlənilir

Deinit loglaması olan bazasınf dərhal geribildirim verir. Ekranın gözlənilən bağlanmasında mesaj görünmədisə — bu sinifdə retain cycle var. Bu təcrübəni bütün ViewController üçün layihə şablonuna əlavə edin.

Tez-tez verilən suallar

Retain cycle GC-dəki yaddaş sızmasından nə ilə fərqlənir?

Retain cycle — güclü istinadların qapalı dairəsinin azad olmanı blokladığı ARC-yə xas problemdir. GC-də toplayıcı kök dəstindən (root set) əlçatanlığı analiz edir, istinad sayğacını deyil — buna görə dövrələr sızma deyil. ARC-də isə hər bir təcrid olunmuş dövrə zəmanətli sızmadır.

Weak istinad retain cycle-i necə qırır?

Weak istinad obyektin retain count-unu artırmır. Əgər dövrədəki strong istinadlardan birini weak ilə əvəz etsəniz, hər bir obyektin retain count-u sıfırlana bilər. Obyekt azad edildikdən sonra weak istinad avtomatik olaraq nil-ə təyin olunur, ölü yaddaşa müraciətin qarşısını alır.

Retain cycle üç və daha çox obyektdən ibarət ola bilərmi?

Bəli, retain cycle istənilən sayda obyekti əhatə edə bilər: A → B → C → A. Azad etmək üçün dövrədə bir halqanı qırmaq kifayətdir — hər hansı strong istinadı weak və ya unowned ilə əvəz edin. Alətlər təkcə cüt obyektləri deyil, bütün qrafiki göstərir.

Nə üçün GCD DispatchWorkItem retain cycle yaratmır?

GCD (Grand Central Dispatch) bağlanmanı icradan sonra saxlamır. DispatchWorkItem icra olunur və azad edilir, hətta bağlanma self-i ələ keçirsə belə. Retain cycle yalnız bağlanma xassə kimi saxlanıldıqda (sinifdə completion handler) yaranır, növbəyə ötürüldükdə deyil.

Instruments tərəfindən aşkarlanmayan retain cycle növləri hansılardır?

Instruments Leaks həmişə müvəqqəti retain cycle-ları (saniyələrlə mövcud olan) və bridge vasitəsilə C/C++ obyektlərindəki tsiklik istinadları tapmır. Tam yoxlama üçün əl ilə Memory Debugger + səhnədəki bütün əsas obyektlərin deinit loglamasından istifadə edin.

Nəticə

  • Retain Cycle — ARC-də obyekt azad olmasını bloklayan strong istinadların qapalı zənciri
  • Səbəblər — strong istinadlı deleqatlar, self ələ keçirən bağlanmalar, ikitərəfli parent-child əlaqələri
  • Həll — bir strong istinadın weak və ya unowned ilə əvəz edilməsi dövrəni qırır
  • Bağlanmalar — saxlanılan completion handler həmişə [weak self] istifadə etməlidir
  • Deleqatlar — həmişə weak; deleqat protokolu AnyObject-dən törəməlidir
  • Diaqnostika — Xcode Memory Debugger, Instruments Leaks, deinit loglaması
  • Profilaktika — biristiqamətli məlumat axını, weak delegate, capture list, deinit ilə bazasınf

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun