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 — 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.
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.
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.
// 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 — 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.
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-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.
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.
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-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 (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 — 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.
Ə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ət | Tip | Nə vaxt tətbiq edilməli |
|---|---|---|
| Memory Debugger | Vizual qrafik | Naviqasiyadan sonra əl ilə yoxlama |
| Instruments Leaks | Avtomatik analiz | Reqresiya testi, CI |
| deinit print | Əl ilə loglama | İnkişaf, code review |
| Malloc Scribble | Runtime 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-in qarşısını almaq onu prodda düzəltməkdən asandır. Tsiklik istinadların riskini minimuma endirən bir neçə qayda.
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.
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.
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.
// 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 — 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 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.
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.
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 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ə
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.
Həm də oxuyun