# UIKit 8: iOS Muammolar va Ularning Aniq Yechimlari — Crash, Concurrency va Memory Buglar

iOS dasturchi kundalik amaliyotida turli xil xatolar, qotishlar (crash) va noxush holatlarga duch keladi. Ushbu darsda ishlab chiqarish (production) muhitida eng ko'p uchraydigan 9 ta jiddiy muammo, ularning **haqiqiy konsol xato matni (exact error text)**, kelib chiqish sababi va Swift 6 hamda zamonaviy UIKit standartlari asosidagi aniq yechimlari tahlil qilinadi.

---

## 1. Swift 6 Concurrency: Nonisolated kontekstdan @MainActor holatini o'zgartirish {#main-actor-mutation}

### Muammo va Xato Matni
Swift 6 to'liq ma'lumotlar poygasi xavfsizligi (complete data-race safety) rejimida quyidagi kod kompilyatorda xatolik beradi:

```text
error: main actor-isolated property 'title' can not be mutated from a nonisolated context
   |         `- note: mutation of this property is only permitted within the actor
   |         `- note: consider declaring an isolated method on 'MainActor' to perform the mutation
```

Yoki UIKit elementini fondagi GCD oqimidan o'zgartirishda:
```text
warning: main actor-isolated property 'items' can not be mutated from a Sendable closure
```

### Sabab
`ProfileViewModel` sinfi `@MainActor` bilan himoyalangan. Biroq `Task.detached` chaqirilganda, u joriy kontekst aktyoridan uzilib, umumiy fon oqimida (nonisolated context) ishga tushadi. Fon oqimidan turib bosh oqim (MainActor) mulkini to'g'ridan-to'g'ri o'zgartirish ma'lumotlar poygasiga (data race) olib keladi.

### Aniq Yechim
Bosh oqim mulkini yangilash uchun `MainActor.run` orqali hop qilish yoki strukturaviy `Task` (u o'z ota kontekstining aktyor izolatsiyasini meros qilib oladi) ishlatiladi:

```swift
import UIKit

@MainActor
public final class ProfileViewModel {
  public var title: String = ""

  public func updateTitle(_ newTitle: String) {
    self.title = newTitle
  }
}

public func updateSafely(model: ProfileViewModel) async {
  // Yechim 1: Strukturaviy Task ota aktyor izolatsiyasini saqlaydi
  Task { @MainActor in
    model.title = "Yangi sarlavha"
  }

  // Yechim 2: Detached task ichidan MainActor.run orqali o'tish
  Task.detached {
    let fetchedTitle = "Serverdan kelgan matn"
    await MainActor.run {
      model.updateTitle(fetchedTitle)
    }
  }
}
```

---

## 2. Swift 6 Concurrency: Non-Sendable turni chegaradan uzatish {#sending-risks-data-race}

### Muammo va Xato Matni
Thread-safe bo'lmagan referens ob'ektini boshqa vazifaga (Task) uzatishda:

```text
error: sending value of non-Sendable type '() async -> ()' risks causing data races [#SendingRisksDataRace]
note: Passing value of non-Sendable type '() async -> ()' as a 'sending' argument to static method 'detached(name:priority:operation:)' risks causing races in between local and caller code
note: access can happen concurrently
```

### Sabab
Oddiy `class ProfileLoader` reference tur bo'lib, uning ichki holati oqimlar o'rtasida himoyalanmagan. Bir vaqtning o'zida ham chaqiruvchi oqimda, ham detached task ichida `loader.name` ga yozish xotirani buzadi.

### Aniq Yechim
1. Qiymat turlaridan (`struct`) foydalanish — barcha maydonlari Sendable bo'lgan struct avtomatik Sendable hisoblanadi.
2. Agar sinf kerak bo'lsa, uni `actor` ga aylantirish yoki `final class` qilib, `Sendable` protokoliga moslash:

```swift
import Foundation

// Yechim A: Qiymat turi (Struct) — ma'lumotlar xavfsiz nusxalanadi
public struct UserProfileData: Sendable {
  public var name: String
  public init(name: String) { self.name = name }
}

// Yechim B: Actor — barcha kirish nuqtalari sinxronizatsiya qilinadi
public actor SafeProfileLoader {
  private var name: String = ""

  public func updateName(_ newName: String) {
    self.name = newName
  }

  public func getName() -> String {
    return self.name
  }
}

public func processData() async {
  let data = UserProfileData(name: "Azimjon")
  Task.detached {
    // Xavfsiz: UserProfileData Sendable
    let localCopy = data
    _ = localCopy.name
  }

  let actorLoader = SafeProfileLoader()
  Task.detached {
    await actorLoader.updateName("Toshkent")
  }
}
```

---

## 3. Crash: Unowned ob'ekt xotiradan o'chgach unga murojaat qilish {#unowned-crash}

### Muammo va Xato Matni
Ilova birdaniga qulab, konsolda quyidagi xatolik chiqadi:

```text
Fatal error: Attempted to read an unowned reference but object 0x1052a5c90 was already destroyed
```
*(yoki `Fatal error: Attempted to read an unowned reference but the object was already deallocated`)*

### Sabab
`unowned` ko'rsatkich ARC'ga quyidagi qat'iy va'dani beradi: *"Ushbu ob'ekt mening butun hayot davrim davomida xotirada mavjud bo'ladi, unga havola hech qachon nil bo'lmaydi"*. 
Ammo agar referent ob'ekt kutilmaganda xotiradan o'chib ketsa (deallocated), `unowned` ko'rsatkich nollanmaydi (`nil` ga aylanmaydi). Uning maydoniga murojaat qilinganda Swift runtime unowned hisoblagichini tekshiradi va ob'ekt yo'q qilinganini ko'rib, dasturni ataylab to'xtatadi.

### Aniq Yechim
Agar ikkita ob'ektning hayot davri (lifecycle) 100% bir vaqtda tugashiga to'liq kafolat bo'lmasa, har doim `weak` ishlatish kerak:

```swift
import Foundation

public final class Company {
  public let name: String = "Tech Corp"
  public init() {}
}

public final class Employee {
  // XAVFLI: unowned let company: Company
  // TO'G'RI: Zaif ko'rsatkich ob'ekt o'chganda avtomatik nil bo'ladi
  public weak var company: Company?

  public init(company: Company?) {
    self.company = company
  }

  public func printCompany() {
    guard let company = company else {
      // Ob'ekt o'chirilgan, qulash bo'lmaydi
      return
    }
    _ = company.name
  }
}
```

---

## 4. Crash: Nil qiymatni majburiy ochish (Force Unwrap) {#force-unwrap-crash}

### Muammo va Xato Matni
Ilova ishlab turgan joyida qulab tushadi:

```text
Fatal error: Unexpectedly found nil while unwrapping an Optional value
```

### Sabab
`!` operatori orqali Optional ichida qiymat bor deb kafolat beriladi, biroq ish vaqtida (runtime) uning qiymati `nil` bo'lib chiqadi. Eng keng tarqalgan sabablar:
- `@IBOutlet` elementiga `viewDidLoad()` dan oldin murojaat qilish;
- Serverdan kelgan JSON modelidagi majburiy maydonning kutilmaganda kelmasligi;
- `Dictionary` indeksidan topilmagan qiymatni `dict["kalit"]!` orqali ochish.

### Aniq Yechim
Majburiy ochish (`!`) o'rniga xavfsiz konstruksiyalar qo'llaniladi:

```swift
import Foundation

public struct UserResponse {
  public let token: String?
}

public func handleLogin(response: UserResponse) -> String {
  // 1. guard let orqali muddatidan oldin qaytish
  guard let token = response.token, !token.isEmpty else {
    return "Xato: Token mavjud emas"
  }

  // 2. Nil-coalescing (??) orqali zaxira qiymat berish
  let sessionName = response.token ?? "Mehmon"
  return "Muvaffaqiyatli: \(sessionName)"
}
```

---

## 5. Crash: Massiv chegarasidan tashqariga chiqish (Index out of range) {#index-out-of-range-crash}

### Muammo va Xato Matni
Ilova to'satdan to'xtab, konsolda ushbu satr paydo bo'ladi:

```text
Swift/ContiguousArrayBuffer.swift:692: Fatal error: Index out of range
```

### Sabab
`array[index]` orqali murojaat qilinganda `index < 0` yoki `index >= array.count` bo'lgan.
Ayniqsa, `UITableViewDataSource` yoki `UICollectionViewDataSource`da:
1. `numberOfRowsInSection` da `items.count` qaytariladi (masalan 10 ta).
2. Qaysidir fon oqimi `items` massivini tozalab tashlaydi (`items.removeAll()`).
3. Asosiy oqim esa `cellForRowAt` da 5-elementni chaqirganda massiv bo'sh bo'ladi va crash ro'y beradi!

### Aniq Yechim
1. Massivlar uchun xavfsiz subscript kengaytmasidan (safe subscript) foydalanish.
2. Ma'lumotlarni o'zgartirish va UI yangilashni faqat `@MainActor` orqali sinxronlashtirish.
3. Yoki zamonaviy `UICollectionViewDiffableDataSource` arxitekturasiga o'tish (unda indeks xatolari umuman bo'lmaydi):

```swift
import Foundation

public extension Collection {
  /// Chegaradan tashqariga chiqsa nil qaytaruvchi xavfsiz indeks
  subscript(safe index: Index) -> Element? {
    return indices.contains(index) ? self[index] : nil
  }
}

public func readItem(at index: Int, from list: [String]) -> String {
  if let item = list[safe: index] {
    return item
  }
  return "Standart element"
}
```

---

## 6. Xotira Sizishi (Retain Cycle) va O'chmaydigan View Controller {#retain-cycles}

### Muammo
Foydalanuvchi ekranni yopadi (`dismiss` yoki `pop`), lekin xotira kamaymaydi. Instruments Memory Graph yoki Leaks orqali tekshirilganda, yopilgan `DetailViewController` xotirada mangu qolib ketgani ko'rinadi. Uning `deinit` bloki hech qachon chaqirilmaydi.

### Sabab
Quyidagi ikkita asosiy sabab kuchli bog'lanish siklini (strong reference cycle) hosil qiladi:
1. **Closure**: `self` closure'ni ushlaydi, closure esa o'z ichida `self`ni kuchli havolada saqlaydi.
2. **Delegate**: Delegat protokoli `weak` deb belgilanmagan.

### Aniq Yechim
Closure'larda doimo `[weak self]` capture list ishlatish, delegat ko'rsatkichlarini esa har doim `weak` qilish:

```swift
import UIKit

public protocol DetailViewDelegate: AnyObject {
  func detailDidFinish()
}

public final class DetailViewController: UIViewController {
  // Delegat har doim weak bo'lishi shart:
  public weak var delegate: DetailViewDelegate?

  public var onCompletion: (() -> Void)?

  public override func viewDidLoad() {
    super.viewDidLoad()

    // RETENTION CYCLE TUZATILDI:
    self.onCompletion = { [weak self] in
      guard let self = self else { return }
      self.view.backgroundColor = .systemBackground
    }
  }

  deinit {
    // Agar barcha bog'lanishlar to'g'ri bo'lsa, bu blok doim ishlaydi
  }
}
```

---

## 7. Auto Layout: Bajarib bo'lmaydigan cheklovlar ziddiyati {#autolayout-conflicts}

### Muammo va Xato Matni
Ilova interfeysi sekinlashadi va konsol qizil ogohlantirishlar bilan to'ladi:

```text
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want: 
(
    "<NSLayoutConstraint:0x600003b28140 UIView:0x10531c.width == 300>",
    "<NSLayoutConstraint:0x600003b28190 UIView:0x10531c.width == 200>"
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600003b28190 UIView:0x10531c.width == 200>
```

### Sabab
View elementiga bir-biriga zid keluvchi cheklovlar berilgan (masalan, bir vaqtning o'zida kengligi 300 va 200 bo'lishi talab qilingan, yoki chap va o'ng chegaralar ota ekranning qat'iy o'lchamiga mos kelmaydi). Auto Layout dvigateli qaysidir cheklovni majburan buzish orqali ekranni chizishga harakat qiladi, bu esa CPU quvvatini yo'qotadi va kutilmagan sakrashlarni keltirib chiqaradi.

### Aniq Yechim
1. Cheklovlarga prioritet berish (`priority`): Majburiy bo'lmagan cheklovni `.defaultHigh` (750) yoki `999` darajasiga tushirish.
2. Cheklovlarga `identifier` berish — shunda konsolda `0x60000...` o'rniga aniq tushunarli nom chiqadi:

```swift
import UIKit

public func setupFlexibleCard(container: UIView, card: UIView) {
  card.translatesAutoresizingMaskIntoConstraints = false
  container.addSubview(card)

  let widthConstraint = card.widthAnchor.constraint(equalToConstant: 300)
  // Identifikator xatoni oson topishga yordam beradi:
  widthConstraint.identifier = "Card_Preferred_Width"
  // Prioritetni 999 qilish orqali ziddiyat paytida bu cheklov o'z o'rnini bo'shatadi:
  widthConstraint.priority = UILayoutPriority(999)

  NSLayoutConstraint.activate([
    card.centerXAnchor.constraint(equalTo: container.centerXAnchor),
    card.centerYAnchor.constraint(equalTo: container.centerYAnchor),
    card.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: 16),
    card.trailingAnchor.constraint(lessThanOrEqualTo: container.trailingAnchor, constant: -16),
    widthConstraint
  ])
}
```

---

## 8. Main Thread Checker: UIKit'ni fon oqimidan chaqirish {#main-thread-checker}

### Muammo va Xato Matni
Xcode orqali ishga tushirilganda, ilova to'xtab qoladi yoki konsolda shunday yozuv chiqadi:

```text
=================================================================
Main Thread Checker: UI API called on a background thread: -[UIView setNeedsLayout]
PID: 28419, TID: 1049283, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21
Backtrace:
4   UIKitCore      0x000000018591d2c0 -[UIView setNeedsLayout] + 68
5   MyApp          0x00000001004128a4 $s5MyApp14DataControllerC10fetchData...
```

### Sabab
UIKit barcha vizual amallarni faqat va faqat **bosh oqimda (Main Thread)** bajarishi shart. Fondagi oqimdan turib `UILabel.text`, `view.setNeedsLayout()` yoki `navigationController.pushViewController` chaqirilsa, grafik dvigatel (Core Animation) poygaga duch keladi va chizish xatolari yuz beradi.

### Aniq Yechim
1. Zamonaviy Swift'da UI bilan ishlovchi barcha klasslarni `@MainActor` bilan belgilash.
2. Agar eski asinxron callback'lardan foydalanilayotgan bo'lsa, UI amallarini `DispatchQueue.main.async` yoki `MainActor.run` ichiga o'rash:

```swift
import UIKit

public final class SafeNetworkService {
  @MainActor
  public static func updateLabelText(_ label: UILabel, with text: String) {
    label.text = text
  }

  public func loadData(completion: @escaping @Sendable (String) -> Void) {
    DispatchQueue.global(qos: .userInitiated).async {
      let result = "Server javobi"
      // Xavfsiz qaytarish:
      DispatchQueue.main.async {
        completion(result)
      }
    }
  }
}
```

---

## 9. TableView va CollectionView Cell Reuse Buglari (Ma'lumotlar oqishi) {#cell-reuse-bugs}

### Muammo
Foydalanuvchi ro'yxatni pastga aylantirganda (scroll):
- 1-qatorda yuklangan rasm to'satdan 15-qatorda ko'rinib qoladi;
- Oldingi katakchaning yoqilgan `UISwitch` holati yangi katakchada ham yoqiq turadi;
- Matnlar va ranglar tartibsiz almashib ketadi.

### Sabab
UIKit xotirani tejash maqsadida katakchalarni qayta ishlatadi (`cell reuse`). Yangi ma'lumot kelganda, o'tgan satrdan qolgan UI holati (masalan, yuklanayotgan asinxron rasm vazifasi) tozalanmagan bo'ladi. Asinxron yuklash tugagach, u allaqachon boshqa foydalanuvchiga tegishli bo'lgan yangi katakchaga o'z rasmini chizib yuboradi.

### Aniq Yechim
1. Katakcha ichidagi barcha o'tkinchi holatlarni `prepareForReuse()` metodida tozalash.
2. Asinxron topshiriqlarni bekor qilish (`cancel()`):

```swift
import UIKit

public final class ProductCell: UITableViewCell {
  private var imageLoadingTask: Task<Void, Never>?

  public let titleLabel = UILabel()
  public let productImageView = UIImageView()

  public override func prepareForReuse() {
    super.prepareForReuse()
    // 1. Matn va rasmni asl holatiga qaytarish:
    titleLabel.text = nil
    productImageView.image = nil

    // 2. Davom etayotgan asinxron tarmoq yuklashini bekor qilish:
    imageLoadingTask?.cancel()
    imageLoadingTask = nil
  }

  public func configure(title: String, imageURL: URL?) {
    titleLabel.text = title

    guard let url = imageURL else { return }

    imageLoadingTask = Task { [weak self] in
      // Rasm yuklash simulyatsiyasi:
      guard !Task.isCancelled else { return }
      // Yuklab olingan rasmni tayinlash:
      self?.productImageView.image = UIImage()
    }
  }
}
```

---

## 10. Virtual Klaviatura va Safe Area bilan To'qnashuv {#keyboard-layout-guide}

### Muammo
Matn kiritish maydoni (`UITextField`) ekran pastida joylashgan bo'lsa, foydalanuvchi klaviaturani ochganda u maydonni to'liq to'sib qo'yadi.

### Eski Usul va Yangi Yechim
Ilgari buning uchun `UIResponder.keyboardWillShowNotification` xabarnomasiga obuna bo'lish, klaviaturaning balandligini (`UIResponder.keyboardFrameEndUserInfoKey`) hisoblash va constraint'larni qo'lda o'zgartirish talab etilar edi.

iOS 15 dan boshlab kiritilgan **`view.keyboardLayoutGuide`** bu muammoni 1 qatorda hal qiladi:

```swift
import UIKit

public func setupInputBar(in viewController: UIViewController, inputBar: UIView) {
  inputBar.translatesAutoresizingMaskIntoConstraints = false
  viewController.view.addSubview(inputBar)

  NSLayoutConstraint.activate([
    inputBar.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
    inputBar.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
    inputBar.heightAnchor.constraint(equalToConstant: 50),

    // Klaviatura ochilganda inputBar avtomatik yuqoriga suriladi:
    inputBar.bottomAnchor.constraint(equalTo: viewController.view.keyboardLayoutGuide.topAnchor)
  ])
}
```

---

## Xulosa va Nazorat Savollari

Ushbu muammolarni oldindan bilish va ularga qarshi to'g'ri arxitektura tanlash ilovaning barqarorligi (crash-free rate) 99.9% dan yuqori bo'lishini ta'minlaydi.

### O'zingizni tekshiring:
1. Nega `unowned` o'rniga `weak` ishlatish xavfsizroq? Ularning xotiradagi ko'rsatkich hisoblagichi (reference count) qanday farqlanadi?
2. `keyboardLayoutGuide` kiritilguniga qadar klaviatura qanday boshqarilar edi?
3. Katakcha qayta ishlatilganda (`prepareForReuse`) nega asinxron vazifalarni bekor qilish (`cancel()`) shart?
4. Auto Layout cheklovlariga `identifier` berishning nosozliklarni bartaraf etishdagi (debugging) foydasi nimada?

---

## Foydali Havolalar
- [UIKit 1: View Ierarxiyasi va Koordinatalar Tizimi](/ios-lessons/uikit-view-hierarchy)
- [UIKit 2: Layout va Render Sikli](/ios-lessons/uikit-layout-cycle)
- [UIKit 3: View Controller Hayot Sikli](/ios-lessons/uikit-vc-lifecycle)
- [UIKit 4: Xotira va ARC](/ios-lessons/uikit-memory)
