Azimjon Abdurasulov

UIKit 4: Xotira boshqaruvi va ARC — Retain Cycle, CoW va Instruments

📱 UIKit va SwiftUI — chuqur

UIKit 4: Xotira boshqaruvi va ARC — Retain Cycle, CoW va Instruments

Holat: 2026-yil sentabr. iOS ilovalarda xotira sizib chiqishi (memory leak) nafaqat ilova tezligini pasaytiradi, balki tizim tomonidan ilovaning majburiy o'ldirilishiga (OOM — Out of Memory Crash) olib keladi. Ushbu darsda ARC qoidalari, real simulyator testlarida o'lchangan 4 ta asosiy Retain Cycle holati, Copy-on-Write (CoW) arxitekturasi va autoreleasepool qo'llanilishi amaliy misollarda ko'rsatiladi.

Mundarija

  1. ARC mexanizmi: Strong, Weak va Unowned
  2. [O'lchangan faktlar: Klojurlar va [weak self]](#closures)
  3. Delegatlar: strong delegate xatosi
  4. Timer va NotificationCenter xotira tuzoqlari
  5. Copy-on-Write (CoW) qanday ishlaydi?
  6. Autoreleasepool: xotira cho'qqilarini (spikes) yo'qotish
  7. Xotira oqishini topish: Memory Graph va Instruments
  8. Xulosa
  9. Manbalar

1. ARC mexanizmi: Strong, Weak va Unowned {#arc-basics}

Swift'da xotira boshqaruvi ARC (Automatic Reference Counting) tomonidan amalga oshiriladi: ob'ektga bo'lgan kuchli havolalar soni 0 ga tushganda, u xotiradan o'chiriladi (deinit).

Havola turiXotira xatti-harakatiQachon ishlatiladi
**strong**Hisoblagichni 1 ga oshiradiOb'ektga egalik qilganda (sukut bo'yicha)
**weak**Hisoblagichni oshirmaydi; ob'ekt o'chganda avtomatik nil bo'ladiDelegatlar, klojurlar, doimo ixtiyoriy (Optional) bo'ladi
**unowned**Hisoblagichni oshirmaydi; ob'ekt o'chganda nil bo'lmaydiIkki ob'ekt bir vaqtda yashab, bir vaqtda o'lishiga 100% ishonch bo'lganda

Ogohlantirish: Agar ob'ekt xotiradan o'chib ketgandan keyin unga unowned orqali murojaat qilinsa, ilova darhol qulaydi: Fatal error: Attempted to read an unowned reference but the object was already deallocated.


2. O'lchangan faktlar: Klojurlar va [weak self] {#closures}

iOS 26.5 simulyatorida XCTest orqali o'tkazilgan eksperiment natijalari (executed_facts.md):

import Foundation

final class DataService {
  var onCompletion: (() -> Void)?
}

final class StrongOwner {
  let service = DataService()
  init() {
    // XATO: self kuchli ushlab qolinadi:
    service.onCompletion = { self.updateUI() }
  }
  func updateUI() {}
}

final class WeakOwner {
  let service = DataService()
  init() {
    // TO'G'RI: [weak self] siklni buzadi:
    service.onCompletion = { [weak self] in self?.updateUI() }
  }
  func updateUI() {}
}

O'lchov natijasi:

  • closure captures self strongly -> owner freed after scope: false (Xotirada abadiy qolib ketadi!).
  • closure captures [weak self] -> owner freed after scope: true (Xotiradan to'g'ri bo'shatiladi).

3. Delegatlar: strong delegate xatosi {#delegates}

Delegat xususiyatini e'lon qilishda eng ko'p uchraydigan xato — weak so'zini unutishdir:

import Foundation

// 1. Protokol AnyObject'dan meros olishi shart:
public protocol PaymentViewDelegate: AnyObject {
  func paymentDidComplete()
}

public final class PaymentView {
  // TO'G'RI: weak bo'lishi shart!
  public weak var delegate: PaymentViewDelegate?

  public init() {}
}

O'lchangan fakt:

  • Agar delegat strong bo'lsa -> screen freed: false (View Controller ekrandan ketsa ham xotirada qoladi).
  • Agar delegat weak bo'lsa -> screen freed: true.

4. Timer va NotificationCenter xotira tuzoqlari {#timer-notifications}

1. Timer tuzog'i

Klassik Timer.scheduledTimer(timeInterval:target:selector:...) o'zining target parametrini Run Loop ichida kuchli (strong) ushlab turadi.

O'lchangan fakt:

  • Timer ishlayotgan paytda controller bo'shatilmaydi (owner freed: false).
  • Faqat timer.invalidate() chaqirilgandagina ob'ekt xotiradan bo'shatiladi (after timer.invalidate() -> owner freed: true)!

Zamonaviy va xavfsiz blokli Timer:

import Foundation

public final class PollingManager {
  private var timer: Timer?

  public func start() {
    // [weak self] bilan blokli Timer Run Loop siklini oldini oladi:
    timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
      self?.pingServer()
    }
  }

  public func stop() {
    timer?.invalidate()
    timer = nil
  }

  private func pingServer() {}

  deinit {
    timer?.invalidate()
  }
}

2. NotificationCenter tuzog'i

  • Selector-based: NotificationCenter.default.addObserver(self, selector: ...) iOS 9 dan boshlab zeroing-weak reference ishlatadi, shuning uchun deinitda removeObserver qilmasa ham retain cycle bo'lmaydi (observer freed: true).
  • Block-based: addObserver(forName:queue:using:) esa o'z ichida selfni kuchli ushlab qolsa, **retain cycle hosil qiladi (observer freed: false)**! Buning oldini olish uchun token saqlanishi va deinit da olib tashlanishi shart.

5. Copy-on-Write (CoW) qanday ishlaydi? {#cow}

Swift'dagi massivlar (Array), lug'atlar (Dictionary) va qatorlar (String) qiymatli turlar (struct) hisoblanadi. Biroq har safar massiv boshqa o'zgaruvchiga berilganda xotira darhol nusxalanmaydi.

O'lchangan ilmiy natija:

import Foundation

func testCopyOnWrite() {
  var a = Array(0..<1000)
  let b = a

  // 1. Mutatsiyadan oldin a va b bitta xotira manziliga qaraydi:
  // Array copy shares storage before mutation: true

  a[0] = 99

  // 2. Yozish (mutatsiya) sodir bo'lgan paytda a uchun alohida nusxa ochiladi:
  // after mutating one copy still shared: false; b[0] = 0
}

Bu Copy-on-Write deb ataladi. U xotirani vaqtinchalik nusxalashdan asraydi va faqat o'zgarish kiritilgan nusxa uchun yangi RAM ajratadi.


6. Autoreleasepool: xotira cho'qqilarini (spikes) yo'qotish {#autoreleasepool}

Katta hajmdagi sikllarda (masalan, 1,000 ta rasmni qayta ishlash) vaqtinchalik yaratilgan CoreGraphics yoki Objective-C ob'ektlari Run Loop tugamaguncha xotirada to'planib qoladi. Natijada xotira birdan $500\text{ MB}$ ga sakrab, ilova qulaydi.

Buning yechimi — **autoreleasepool**:

import Foundation

func processBatchImages(urls: [URL]) {
  for url in urls {
    autoreleasepool {
      // Har bir aylanmada hosil bo'lgan vaqtinchalik xotira
      // blok yakunlanishi bilan darhol tozalanadi:
      let _ = try? Data(contentsOf: url)
    }
  }
}

7. Xotira oqishini topish: Memory Graph va Instruments {#diagnostics}

Retain cycle'larni topishning 3 ta asosiy yo'li:

  1. **deinit ichida log qo'yish:** Har bir controller va servisda deinit { print("\(Self.self) bo'shatildi") } bo'lishi kerak. Agar ekrandan chiqqanda bu yozuv chiqmasa — retain cycle bor.
  2. Xcode Memory Graph Debugger: Pastki panelda 3 ta doiracha belgisi. U ob'ektlar o'rtasidagi kuchli havolalar zanjirini vizual ko'rsatadi va shubhali sikllarni binafsha rangli ogohlantirish bilan belgilaydi.
  3. Instruments (Leaks & Allocations): Ilovani profayl qilish uchun eng kuchli vosita bo'lib, xotira hajmining o'sish dinamikasini real vaqt rejimida qayd etadi.

8. Xulosa {#xulosa}

  • Retain cycle ikkita ob'ekt bir-birini kuchli (strong) ushlab turganda yuzaga keladi.
  • Klojurlarda [weak self] ishlatish va delegatlarni weak qilish xotira xavfsizligining oltin qoidasidir.
  • unowned faqat ikkinchi ob'ekt birinchisidan oldin hech qachon o'chmasligiga 100% kafolat bo'lganda qo'llanilishi kerak, aks holda crash beradi.
  • Blokli NotificationCenter va Target-Action Timer doimo qo'lda bekor qilinishi (invalidate / removeObserver) shart.
  • Swift CoW orqali faqat ma'lumot o'zgargandagina yangi xotira ajratadi.
  • Sikllardagi xotira cho'qqilarini yo'qotish uchun autoreleasepool dan foydalaniladi.

Manbalar {#manbalar}

  1. Automatic Reference Counting — Swift Language Guide
  2. Finding Memory Leaks with Instruments — Apple Developer
  3. Timer.scheduledTimer Documentation — Apple
  4. Using Autorelease Pool Blocks — Apple Archive

Markdown manba

iOS Lessons