Retain Cycle — mohiyati, yuzaga kelish sabablari va dastur ishlab chiqishda bartaraf etish

Muallif: IT Sectr Nashr etilgan: 2026-03-29 O'qish vaqti: 8 daq

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 — strong havolalarning yopiq zanjiri, obyektlarni ARC tomonidan bo'shatib bo'lmaydi
  • Sabab — ikki (yoki undan ortiq) obyekt bir-biriga strong havola saqlaydi, retain countni nolga tushirish mumkin emas
  • Natijalar — xotira oqishi: obyektlar abadiy xotirada qoladi, RAM iste'moli oshadi
  • Yechim — sikldagi strong havolalardan birini weak yoki unowned bilan almashtirish
  • Diagnostika — Xcode Memory Debugger, Instruments Leaks, Debug Memory Graph

Retain Cycle nima?

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.

iOS ishlab chiqishda retain cycle misollari

Har bir iOS dasturchisi duch keladigan klassik retain cycle stsenariylarini ko'rib chiqaylik. Ushbu naqshlarni tushunish ARC bilan xavfsiz kod yozishning asosidir.

Delegat bilan Parent-Child

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.

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

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.

Qatlamli arxitekturalar

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.

Swift yopilmalarida Retain Cycle

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.

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

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

Yopilmalarda Unowned self

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 qanday aniqlash mumkin: diagnostika vositalari

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

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

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.

Deinit loglash

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.

VositaTurQachon qo'llaniladi
Memory DebuggerVizual grafikNavigatsiyadan keyin qo'lda tekshirish
Instruments LeaksAvtomatik tahlilRegressiya testi, CI
deinit printQo'lda loglashIshlab chiqish, code review
Malloc ScribbleRuntime bayrog'iUse-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 cycle profilaktikasi va best practices

Retain cyclening oldini olish uni prodda tuzatishdan osonroqdir. Tsiklik havolalar xavfini minimal darajaga tushiradigan bir necha qoidalar.

Weak delegate qoidasi

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.

Yopilmalarda capture list

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.

Arxitekturani tekshirish

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.

swift
// 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 GCdagi xotira oqishidan nima bilan farq qiladi?

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 retain cycleni qanday uzadi?

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.

Retain cycle uch yoki undan ortiq obyektdan iborat bo'lishi mumkinmi?

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.

Nega GCD DispatchWorkItem retain cycle yaratmaydi?

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 tomonidan aniqlanmaydigan retain cycle turlari qaysilar?

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

  • Retain Cycle — ARCda obyektlarni bo'shatishni bloklaydigan strong havolalarning yopiq zanjiri
  • Sabablar — strong havolali delegatlar, selfni tutib oluvchi yopilmalar, ikki tomonlama parent-child aloqalari
  • Yechim — bir strong havolani weak yoki unowned bilan almashtirish siklni uzadi
  • Yopilmalar — saqlangan completion handler har doim [weak self] ishlatishi kerak
  • Delegatlar — har doim weak; delegat protokoli AnyObject dan voris bo'lishi kerak
  • Diagnostika — Xcode Memory Debugger, Instruments Leaks, deinit loglash
  • Profilaktika — bir yo'nalishli ma'lumot oqimi, weak delegate, capture list, deinitli bazaviy sinf

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.

Loyihani muhokama qilish

Shuningdek o'qing