# UIKit 2: Layout va render sikli — runloop, updateProperties, Auto Layout va 120 Hz

> **Holat:** 2026-yil sentabr. iOS ilovalarda animatsiyalarning qotishi (stutter) va xatoliklar asosan Run Loop va render siklini noto'g'ri tushunishdan kelib chiqadi. Ushbu darsda cheklovlar (constraints), joylashuv (layout) va chizish (display) bosqichlari, `setNeedsLayout` ning coalescing mexanizmi, iOS 26 da qo'shilgan yangi `updateProperties` metodi hamda 120 Hz ProMotion talablari ko'rib chiqiladi.

## Mundarija

1. [Run Loop va render siklining 3 bosqichi](#render-loop)
2. [Coalescing: setNeedsLayout va layoutIfNeeded mexanizmi](#coalescing)
3. [iOS 26 yangiligi: updateProperties va uning tartibi](#update-properties)
4. [Auto Layout anatomiyasi: Hugging va Compression Resistance](#auto-layout)
5. [Intrinsic Content Size va dinamik o'lchamlar](#intrinsic-size)
6. [Display pass: draw(_:) dan qachon qochish kerak?](#display-pass)
7. [120 Hz ProMotion displeylari va CADisplayLink](#promotion)
8. [Xulosa](#xulosa)
9. [Manbalar](#manbalar)

---

## 1. Run Loop va render siklining 3 bosqichi {#render-loop}

Foydalanuvchi ekranga tekkanda yoki tarmoqdan ma'lumot kelganda, UIKit darhol ekranni qayta chizmaydi. Har bir Run Loop aylanmasining oxirida **Render Loop** ishga tushadi:

```text
[Run Loop hodisasi] (Teginish, Timer, Tarmoq)
        |
        v
+-------------------------------------------------------+
| 1. Constraints Pass: updateConstraints()              |
|    (Cheklovlar hisob-kitobi: yuqoridan pastga)        |
+-------------------------------------------------------+
        |
        v
+-------------------------------------------------------+
| 2. Layout Pass: updateProperties() -> layoutSubviews()|
|    (Frame va bounds o'rnatilishi: pastdan yuqoriga)   |
+-------------------------------------------------------+
        |
        v
+-------------------------------------------------------+
| 3. Display Pass: draw(_:) / CoreAnimation Commit      |
|    (Piksellarni GPU'ga uzatish va ekranga chiqarish)  |
+-------------------------------------------------------+
```

![RunLoop va Render Siklining 3 Bosqichi](/lessons/images/uikit/uikit_render_loop_runloop.svg)

---

## 2. Coalescing: setNeedsLayout va layoutIfNeeded mexanizmi {#coalescing}

iOS samaradorligining siri — **birlashtirish (coalescing)** qobiliyatidadir.

Haqiqiy o'lchov natijalari (iOS 26.5 simulyatorida CountingView):
- Agar `setNeedsLayout()` chaqirilmagan bo'lsa, `layoutIfNeeded()` chaqirilganda qo'shimcha `layoutSubviews()` chaqiruvi soni: **`0`**.
- Agar ketma-ket **3 marta `setNeedsLayout()`** chaqirilib, so'ng `layoutIfNeeded()` bajarilsa, `layoutSubviews()` chaqiruvi soni: **`1 marta`**!

```swift
import UIKit

@MainActor
func demonstrateCoalescing(view: UIView) {
  // Bu chaqiruvlar 3 marta qayta chizishga olib kelmaydi:
  view.setNeedsLayout()
  view.setNeedsLayout()
  view.setNeedsLayout()

  // Faqat 1 marta barcha o'zgarishlar bitta siklda bajariladi:
  view.layoutIfNeeded()
}
```

- **`setNeedsLayout()`:** "Men kirlendim (dirty), keyingi freymda meni qayta hisobla" deb bayroqcha qo'yadi. Juda tez va yengil amal.
- **`layoutIfNeeded()`:** Agar dirty bayrog'i bo'lsa, Run Loop oxirini kutmasdan darhol shu zahoti `layoutSubviews()` ni chaqiradi. Animatsiya bloklari ichida (`UIView.animate`) frame o'zgarishini majburiy kiritish uchun aynan `layoutIfNeeded()` ishlatiladi.

---

## 3. iOS 26 yangiligi: updateProperties va uning tartibi {#update-properties}

iOS 26.0 (Xcode 26.5) bilan UIKit'ga yangi **`updateProperties`** API'si qo'shildi ([sdk_facts.md](file:///Users/codingtech/Documents/GitHub/azimjondev.uz/lessons-handoff/research-uikit/sdk_facts.md)):
- `setNeedsUpdateProperties()`
- `updateProperties()` (talab: `super.updateProperties()` chaqirilishi shart)
- `updatePropertiesIfNeeded()`

Ushbu metod view xususiyatlarini (ranglar, matnlar, holatlar) layout hisobidan ajratish uchun xizmat qiladi.

### O'lchangan tartib (iOS 26.5 simulyatori):
Ikkala bayroqcha ham yoqilganda (`setNeedsUpdateProperties()` va `setNeedsLayout()`), `layoutIfNeeded()` bajarilganda chaqirilish tartibi:

$$\text{Order} = [\text{"updateProperties"}, \text{"layoutSubviews"}]$$

`updateProperties()` doimo **`layoutSubviews()` dan oldin** bajariladi! Bu view o'z xususiyatlariga (masalan, matn uzunligiga) qarab o'lchamlarini hisoblab olishi uchun qulaydir.

---

## 4. Auto Layout anatomiyasi: Hugging va Compression Resistance {#auto-layout}

Auto Layout tizimi Cassowary chiziqli tengsizliklar algoritmi asosida ishlaydi. Ikkita qarama-qarshi kuch mavjud:

| Prioritet turi | Standart qiymat | Ma'nosi | Xulq-atvori |
| :--- | :---: | :--- | :--- |
| **Content Hugging Priority (CHP)** | 250 (Low) | *"Meni o'z o'lchamimdan kattalashtirib yuborma"* | Agar ikkita label qatorda tursa, CHP yuqori bo'lgani o'z o'lchamida qoladi, past bo'lgani esa cho'ziladi. |
| **Content Compression Resistance (CCRP)** | 750 (High) | *"Meni o'z o'lchamimdan kichiklashtirma, matnimni kesma"* | Agar joy yetishmasa, CCRP yuqori bo'lgan label to'liq ko'rinadi, past bo'lgani esa uch nuqta (`...`) bo'lib qisqaradi. |

```swift
import UIKit

@MainActor
func configureLabelPriorities(titleLabel: UILabel, badgeLabel: UILabel) {
  // Badge doimo o'z hajmida qolishi va cho'zilmasligi shart:
  badgeLabel.setContentHuggingPriority(.required, for: .horizontal)
  badgeLabel.setContentCompressionResistancePriority(.required, for: .horizontal)

  // Title bo'sh joy qolmasa qisqarishi mumkin:
  titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
  titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
}
```

---

## 5. Intrinsic Content Size va dinamik o'lchamlar {#intrinsic-size}

**`intrinsicContentSize`** — view o'zining ichki mazmuniga qarab qanday hajm egallashni xohlashini bildiradi (masalan, `UILabel` matn hajmiga, `UIImageView` esa rasm piksellariga qarab).

Agar o'zgaruvchi komponent qursangiz:

```swift
import UIKit

public final class RoundedBadgeView: UIView {
  public var text: String = "" {
    didSet {
      // Auto Layout'ga ichki hajm o'zgarganini xabar qilamiz:
      invalidateIntrinsicContentSize()
    }
  }

  public override var intrinsicContentSize: CGSize {
    let size = (text as NSString).size(withAttributes: nil)
    return CGSize(width: size.width + 16, height: size.height + 8)
  }
}
```

---

## 6. Display pass: draw(_:) dan qachon qochish kerak? {#display-pass}

`draw(_:)` metodini qayta yozish (override qilish) eng oxirgi chora bo'lishi kerak.

**Nega?**
- `draw(_:)` chaqirilganda, iOS xotirada ekranning to'liq o'lchamiga mos **Backing Store (RGBA bitmap)** ochadi. Masalan, iPhone 17 Pro Max ekranida ($1320 \times 2868$ piksel) bitta butun ekranli view $15\text{ MB}$ dan ortiq toza RAM yeydi!
- `CALayer` xususiyatlari (`backgroundColor`, `cornerRadius`, `borderWidth`, `shadowPath`) esa GPU darajasida hech qanday qo'shimcha xotira sarflamasdan ishlaydi.

Faqat CoreGraphics orqali maxsus matematik grafiklar yoki egri chiziqlar (Bezier Path) chizish zarur bo'lgandagina `draw(_:)` ishlatilishi kerak.

---

## 7. 120 Hz ProMotion displeylari va CADisplayLink {#promotion}

Zamonaviy iPhone (Pro modellari) va iPad qurilmalari ProMotion texnologiyasiga ega:

- **60 Hz displeylar:** Freym vaqti — **$16.6\text{ ms}$**.
- **120 Hz displeylar:** Freym vaqti — atigi **$8.3\text{ ms}$**!

Agar sizning `layoutSubviews()` yoki hisob-kitoblaringiz $8.3\text{ ms}$ dan oshib ketsa, qurilma darhol kadrni tashlab yuboradi (dropped frame) va animatsiya qotadi.

Maxsus silliq animatsiyalar uchun **`CADisplayLink`** dan foydalaniladi:

```swift
import UIKit

public final class AnimationDriver: NSObject {
  private var displayLink: CADisplayLink?

  public func start() {
    displayLink = CADisplayLink(target: self, selector: #selector(step))
    // 120 Hz gacha tezlikni so'raymiz:
    displayLink?.preferredFrameRateRange = CAFrameRateRange(
      minimum: 60,
      maximum: 120,
      preferred: 120
    )
    displayLink?.add(to: .main, forMode: .common)
  }

  @objc private func step(link: CADisplayLink) {
    // link.targetTimestamp - keyingi kadr vaqti
  }

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

---

## 8. Xulosa {#xulosa}

- Render Loop uchta qat'iy bosqichdan iborat: Constraints Pass -> Layout Pass -> Display Pass.
- `setNeedsLayout()` chaqiruvlari coalescing orqali birlashtiriladi va bitta freymda faqat bir marta `layoutSubviews()` bajariladi.
- iOS 26 ning `updateProperties()` metodi view holatini layoutdan ajratadi va doimo `layoutSubviews()` dan oldin chaqiriladi.
- Auto Layout'da Content Hugging cho'zilmaslikni, Compression Resistance esa qisqarmaslikni boshqaradi.
- `draw(_:)` ortiqcha xotira (backing store) sarflagani tufayli, iloji boricha `CALayer` xususiyatlaridan foydalanish zarur.
- 120 Hz ProMotion sharoitida har bir freym $8.3\text{ ms}$ dan kam vaqtda bajarilishi shart.

---

## Manbalar {#manbalar}

1. [Auto Layout Guide — Apple Documentation Archive](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html)
2. [UIView layoutSubviews() — Apple Documentation](https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews)
3. [Optimizing ProMotion Performance — Apple Documentation](https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro)
4. [High Performance Auto Layout — WWDC](https://developer.apple.com/videos/play/wwdc2018/220/)
