Retain Cycle (tsiklik havola) — ARCda ikki yoki undan ortiq obyektlar bir-biriga strong havolalar orqali murojaat qilib, yopiq sikl hosil qiladigan holatdir. Apple Memory Management Guide, 2026 ma'lumotlariga ko'ra, retain cycle sikldagi barcha obyektlarning bo'shatilishini bloklaydi, chunki har birining retain count ≥ 1. GCdagi xotira oqishidan farqli o'laroq, retain cycle siklning kamida bitta tashqi ishtirokchisi tirik ekan, obyektlarni tirik saqlaydi — va hatto barcha tashqi havolalar yo'qolganidan keyin ham, agar sikl izolyatsiya qilingan bo'lsa.
Asosiy fikrlar
Retain Cycle — ikki yoki undan ortiq obyektlarning strong havolalar orqali bir-biriga egalik qiladigan, yopiq bog'liqlik grafigini yaratadigan holatdir. ARC bu obyektlarning hech birini bo'shata olmaydi, chunki har birining retain count har doim ≥ 1: A obyekti Bni, B esa Ani ushlab turadi va ularning hisoblagichlari hech qachon nolga tushmaydi.
Muammo faqat havolalarni hisoblash tizimlarida (ARC, MRR) yuzaga keladi. Garbage Collectionda yig'uvchi ildiz to'plamidan (root set) havola grafigi bo'yicha erishib bo'lmaslikni aniqlaydi — sikllar to'siq emas. ARCda esa sikl oqish bilan teng, chunki deterministik bo'shatish hisoblagichga asoslanib aylanma bog'liqlikni hal qila olmaydi.
WWDC 2012 Session 406 ma'lumotlariga ko'ra, retain cycle Objective-C va Swift ilovalarida xotira oqishining eng keng tarqalgan sababidir. Odatdagi stsenariylar: delegatlar bilan parent-child munosabatlari, selfni tutib oluvchi yopilmalar va ikki tomonlama aloqalarga ega qatlamli arxitekturalar.
Har bir iOS dasturchisi duch keladigan klassik retain cycle stsenariylarini ko'rib chiqaylik. Ushbu naqshlarni tushunish ARC bilan xavfsiz kod yozishning asosidir.
Klassik stsenariy: ota-ona obyekt (masalan, UIViewController) bola obyekt yaratadi va uning delegati bo'ladi. Agar ikkalasi strong havolalardan foydalansa, retain cycle yuzaga keladi. Yechim — delegat weak bo'lishi kerak.
// XATO: strong delegate orqali 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 orqali strong)
} // ⚠️ Retain cycle!
class ChildVC: UIViewController {
var delegate: ChildDelegate? // ❌ standart strong
}
// TUZATISH: weak delegate
class ChildVC: UIViewController {
weak var delegate: ChildDelegate? // ✅ weak — ushlab qolmaydi
}
Misolda ParentVC child xususiyati orqali ChildVCga strong havola saqlaydi. ChildVC delegate orqali ParentVCga strong havola saqlaydi. Sikl yopilgan. Tuzatish: weak var delegate — havola retain countni oshirmaydi va ParentVC bo'shatilishi mumkin.
NSTimer — klassik retain cycle manbai. Taymer targetni (odatda self) ushlab turadi, target esa taymerni xususiyat orqali ushlab turadi. Taymer bir martalik bo'lsa ham, invalidategacha bo'shatilmaydi. Yechim: har doim timer.invalidate() chaqiring deinit yoki viewDidDisappear da.
Kaskad egalik arxitekturalarida (koordinatorlar, routerlar) ko'p bosqichli sikllar tez-tez paydo bo'ladi: Coordinator → ViewController → ViewModel → Coordinator (callback orqali). Zanjirdagi har bir strong havola ongli ravishda tanlanishi kerak — bitta weak havola istalgan bo'g'inda siklni uzadi.
Swiftdagi yopilmalar (closures) tashqi o'zgaruvchilarni strong havola orqali tutib oladi. Agar yopilma obyektning xususiyati sifatida saqlansa (masalan, completion handler) va selfni tutib olsa, retain cycle yuzaga keladi: self → closure → self.
Bu zamonaviy Swift ishlab chiqishda eng keng tarqalgan retain cycle manbai. U yashirincha yuzaga keladi — dasturchi yopilmada selfning tutib olinishini sezmasligi mumkin, ayniqsa aniq selfsiz qisqartirilgan sintaksisdan foydalanganda.
class DownloadService {
var onComplete: ((Data) -> Void)?
var result: Data?
func startDownload() {
// ❌ Retain cycle: self → onComplete → self
onComplete = { data in
self.result = data
self.notifyUI()
}
// ✅ Tuzatish: weak self bilan capture list
onComplete = { [weak self] data in
guard let self else { return }
self.result = data
self.notifyUI()
}
}
func notifyUI() { }
}
Capture list [weak self] yopilma ichida selfga zaif havola yaratadi. Agar DownloadService yopilma bajarilishidan oldin bo'shatilsa, self nil bo'ladi va kod guard orqali xavfsiz chiqadi. Bu Swiftdagi asinxron yopilmalar uchun standart naqshdir — yopilma xususiyat sifatida saqlanganda har doim qo'llanilishi kerak.
unowned self — weak selfga alternativ, self yopilmadan uzoqroq yashashiga kafolat bo'lganda ishlatiladi. Misol: darhol bajariladigan sinxron yopilma (sorted, filter). Bunday hollarda self albatta tirik va unowned xavfsiz. Biroq unowned bo'shatilgan obyektga murojaat qilganda crash beradi — shuning uchun weak xavfsiz tanlov hisoblanadi.
Retain cycleni erta bosqichda aniqlash ilova unumdorligi uchun juda muhimdir. iOS ishlab chiqishda tsiklik havolalarni aniqlashning asosiy vositalari va usullarini ko'rib chiqaylik.
Xcode Memory Debugger (Debug Memory Graph) — xotiradagi obyektlar grafigini ularning havolalari bilan ko'rsatadigan vizual vositadir. Retain cycle strong o'qlarining yopiq zanjiri sifatida ko'rsatiladi. Ishga tushirish uchun: ilova ishlayotganda Debug area panelidagi Debug Memory Graph tugmasini bosing. Har bir obyekt turi, manzili va havolalar ro'yxati bilan ko'rsatiladi.
Instruments Leaks — oqishlarni avtomatik aniqlash uchun profiler. AJratishlarni qayd etadi va havola grafigini real vaqtda tahlil qiladi. Faqat retain cycle emas, balki unutilgan havolalar, bo'shatilmagan ViewControllerlar va boshqa oqishlarni ham aniqlaydi. Leaks aniq obyektni va ushlab turish zanjirini ko'rsatadi.
Eng oddiy usul — har bir asosiy sinfning deinitiga print qo'shing. Agar obyektning kutilgan yo'q qilinishida deinit chaqirilmasa — retain cycle mavjud. Bu usul vositalarni talab qilmaydi va boshlang'ich diagnostika uchun samaralidir.
| Vosita | Tur | Qachon qo'llaniladi |
|---|---|---|
| Memory Debugger | Vizual grafik | Navigatsiyadan keyin qo'lda tekshirish |
| Instruments Leaks | Avtomatik tahlil | Regressiya testi, CI |
| deinit print | Qo'lda loglash | Ishlab chiqish, code review |
| Malloc Scribble | Runtime bayrog'i | Use-after-free tuzatish |
Tavsiya etilgan yondashuv: ishlab chiqish bosqichida deinit loglash, qo'lda test qilishda Memory Debugger, CI/CD pipeline-da avtomatik regressiya oqish nazorati uchun Instruments Leaksdan foydalaning.
Retain cyclening oldini olish uni prodda tuzatishdan osonroqdir. Tsiklik havolalar xavfini minimal darajaga tushiradigan bir necha qoidalar.
Barcha delegatlar va dataSource weak bo'lishi kerak. Bu qoida UIKit-ga o'rnatilgan: Apple SDKdagi barcha delegat protokollari weak xususiyatlar bilan e'lon qilingan (UITableView.delegate, UICollectionView.dataSource). O'z protokollaringiz uchun weak var delegate: MyDelegate? dan foydalaning va protokolni AnyObject dan voris qiling.
Xususiyat sifatida saqlanadigan (completion handler, callback) va selfni tutib oladigan har bir yopilma capture listda [weak self] ishlatishi kerak. Istisno — darhol bajariladigan va saqlanmaydigan yopilmalar (sorted, map, filter). Ular uchun unowned self xavfsiz.
Murakkab arxitekturalarda (VIPER, Coordinators, Redux) strong havolalar yo'nalishini kuzatib boring. Egasi bo'ysunuvchiga strong havola saqlaydi, lekin bo'ysunuvchi egasiga faqat weak yoki unowned orqali murojaat qilishi kerak. Bir yo'nalishli ma'lumot oqimi (unidirectional data flow) havolalarni nazorat qilishni osonlashtiradi.
// Misol: deinit loglash bilan tekshirish
class BaseViewController: UIViewController {
deinit {
print("✅ \(type(of: self)) deallocated")
}
}
// Foydalanish: barcha ViewControllerlar BaseViewController dan voris oladi
class ProfileVC: BaseViewController {
var viewModel: ProfileViewModel?
var onLogout: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onLogout = { [weak self] in
self?.dismiss(animated: true)
}
}
}
// ProfileVC yopilganda konsolda "✅ ProfileVC deallocated" kutiladi
Deinit loglash bilan bazaviy sinf darhol qayta aloqa beradi. Agar xabarni ekranning kutilgan yopilishida ko'rinmasa — bu sinfda retain cycle mavjud. Ushbu amaliyotni barcha ViewControllerlar uchun loyiha shabloniga qo'shing.
Tez-tez so'raladigan savollar
Retain cycle — strong havolalarning yopiq doirasi bo'shatishni bloklaydigan ARCga xos muammo. GCda yig'uvchi ildiz to'plamidan (root set) erishish mumkinligini tahlil qiladi, havola hisoblagichini emas — shuning uchun sikllar oqish emas. ARCda esa har bir izolyatsiya qilingan sikl kafolatlangan oqishdir.
Weak havola obyektning retain countini oshirmaydi. Agar sikldagi strong havolalardan birini weak bilan almashtirsangiz, har bir obyektning retain counti nolga tushishi mumkin. Obyekt bo'shatilgandan so'ng, weak havola avtomatik ravishda nilga o'rnatiladi, o'lik xotiraga murojaat qilishning oldini oladi.
Ha, retain cycle istalgan miqdordagi obyektni o'z ichiga olishi mumkin: A → B → C → A. Bo'shatish uchun siklda bitta bo'g'inni uzish kifoya — istalgan strong havolani weak yoki unowned bilan almashtiring. Vositalar faqat juft obyektlarni emas, balki butun grafikni ko'rsatadi.
GCD (Grand Central Dispatch) yopilmani bajarilgandan keyin saqlamaydi. DispatchWorkItem bajariladi va bo'shatiladi, hatto yopilma selfni tutib olgan bo'lsa ham. Retain cycle faqat yopilma xususiyat sifatida saqlanganda (sinfdagi completion handler) paydo bo'ladi, navbatga uzatilganda emas.
Instruments Leaks har doim ham vaqtinchalik retain cyclelarni (bir necha soniya mavjud bo'lgan) va bridge orqali C/C++ obyektlaridagi tsiklik havolalarni topa olmaydi. To'liq tekshirish uchun qo'lda Memory Debugger + sahnadagi barcha asosiy obyektlarning deinit loglashidan foydalaning.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.