08. Performance, Caching va 120 FPS
⚡ Server-Driven UI (SDUI) — Mukammal Enterprise & System Design
08. Performance, Caching va 120 FPS ProMotion: Zero-Jank Arxitekturasi
"Foydalanuvchi ilovangiz SDUI ekanini bilmasligi kerak. Agar sahifani aylantirganda (scroll) mikro-qotish (jank) sezilsa yoki ekranni ochish 1 soniyadan ortiq vaqt olsa — arxitekturangiz yutqazdi."
1. Zero-Jank Qoidasi: ProMotion 8.33 ms Kadr Byudjeti
iPhone 13 Pro va undan keyingi barcha zamonaviy Apple qurilmalarida ekran yangilanish chastotasi 120 Hz (ProMotion) ni tashkil etadi. Bu har bir kadr ekranga chizilishi uchun protsessorga bor-yo'g'i 8.33 millisekund vaqt berilishini anglatadi:
$$\text{Frame Budget} = \frac{1000\text{ ms}}{120\text{ fps}} \approx 8.33\text{ ms}$$
Agar siz 60 KB lik SDUI JSON faylini Main Thread (asosiy oqim)da pars qilsangiz:
JSONDecoder().decode(...)o'rtacha 18–25 ms vaqt oladi.- Natijada 2-3 ta kadr tashlab ketiladi (Dropped Frames).
- Foydalanuvchi barmoq bilan sahifani yurgizganda qotish (Jank/Stutter) yuz beradi!
flowchart TD
subgraph BadThread["❌ Asosiy Oqimda (Main Thread) Parslash"]
direction TB
M1["Main Thread (UI + Touch + Render)"]
M1 -->|"JSONDecoder.decode() ishga tushdi (22 ms)"| Frozen["⚠️ MAIN THREAD 22 ms GA MUZLADI!\n2 ta kadr yo'qoldi (Dropped Frames)\nFoydalanuvchi ekranning qotganini ko'radi!"]
end
subgraph GoodThread["✅ Swift 6 Background Actor Bilan"]
direction TB
Main["Main Thread: 120 FPS Silliq UI"]
Actor["Background DataEngine Actor"]
Main -.->|"URL request jo'natildi"| Actor
Actor -->|"Fonda parslash va autorelease (20 ms)"| Actor
Actor -->|"Tayyor [ComponentNode] yuborildi"| Main
Main -->|"0.05 ms: Faqat View yangilandi"| Smooth["🚀 120 FPS Silliq Scrolling!"]
end
style BadThread fill:#251014,stroke:#f87171,stroke-width:1px
style GoodThread fill:#0d1f14,stroke:#4ade80,stroke-width:1px
2. Swift 6 Background Actor bilan Parallallashtirish
JSON'ni tarmoqdan yuklab olish va Swift modellariga o'g'irish 100% fon oqimida (Background Actor) bajarilishi shart:
import Foundation
/// Swift 6 Concurrency asosidagi xavfsiz fon aktori
public actor SDUIDataEngine {
public static let shared = SDUIDataEngine()
private let decoder: JSONDecoder
public init() {
self.decoder = JSONDecoder()
}
/// To'liq background thread'da parslash
public func parseScreen(from data: Data) throws -> SDUIScreenPayload {
// Fon oqimida vaqtinchalik xotirani darhol tozalash uchun autoreleasepool
try autoreleasepool {
try decoder.decode(SDUIScreenPayload.self, from: data)
}
}
}
UI qatlamida esa faqat tayyor modellar @MainActor ga uzatiladi:
@MainActor
public func loadScreen(from url: URL) async {
do {
let (data, response) = try await URLSession.shared.data(from: url)
// 1. Fon oqimida parslash (Main thread ozod!)
let parsedScreen = try await SDUIDataEngine.shared.parseScreen(from: data)
// 2. Asosiy oqimda faqat View'ni yangilash (0.05 ms)
self.components = parsedScreen.components
} catch {
self.handleError(error)
}
}
3. Stale-While-Revalidate (SWR) Kesh Strategiyasi
Foydalanuvchi metroda yoki internet sust joyda ilovani ochganda bo'sh oq ekran ko'rmasligi kerak:
sequenceDiagram
autonumber
participant User as Foydalanuvchi
participant UI as SwiftUI View
participant Cache as SDUICacheManager (Disk)
participant Server as Backend API
User->>UI: Ilovani ochadi
UI->>Cache: Oxirgi snapshot bormi?
Cache-->>UI: Ha! (data, eTag: "v2.4")
UI-->>User: 0 ms: Ekranni darhol keshdan chizadi (Instant Launch!)
UI->>Server: Orqa fonda: GET /screen (If-None-Match: "v2.4")
alt Ma'lumot o'zgarmagan bo'lsa
Server-->>UI: 304 Not Modified (0 bayt data!)
else Yangi o'zgarish bo'lsa
Server-->>UI: 200 OK + Yangi JSON
UI->>Cache: Yangi snapshotni diskka yozish
UI->>UI: Faqat o'zgargan qismlarni animatsiya bilan yangilash
UI-->>User: Yangilangan UI ko'rinadi
end
4. Swift 6 Kesh Boshqaruvchisi Kodi
import Foundation
public final class SDUICacheManager: Sendable {
public static let shared = SDUICacheManager()
private let fileManager = FileManager.default
private let cacheDirectory: URL
private init() {
let paths = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
self.cacheDirectory = paths[0].appendingPathComponent("SDUISnapshots")
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
}
public func saveSnapshot(data: Data, eTag: String?, for endpoint: String) {
let fileURL = cacheDirectory.appendingPathComponent(endpoint.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "cache")
try? data.write(to: fileURL)
if let eTag = eTag {
UserDefaults.standard.set(eTag, forKey: "etag_\(endpoint)")
}
}
public func getCachedSnapshot(for endpoint: String) -> (data: Data, eTag: String?)? {
let fileURL = cacheDirectory.appendingPathComponent(endpoint.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "cache")
guard let data = try? Data(contentsOf: fileURL) else { return nil }
let eTag = UserDefaults.standard.string(forKey: "etag_\(endpoint)")
return (data, eTag)
}
}
5. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Nega parslash jarayonida 'autoreleasepool' blokini ishlatish xotira to'lib ketishining oldini oladi?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: JSONDecoder parslash jarayonida millionlab kichik vaqtinchalik String, Substring va Dictionary obyektlarini yaratadi. Odatda bu obyektlar RunLoop tugaguncha xotirada qolib ketadi. autoreleasepool { ... } ishlatilganda, blok yakunlanishi bilanoq barcha vaqtinchalik obyektlar darhol RAM'dan o'chiriladi va xotira cho'qqisi (Memory Spike) sababli ilovaning iOS tomonidan o'ldirilishi (Jetsam OOM) oldi olinadi. </details>
<details> <summary><b>2-Savol: HTTP 304 Not Modified statusi SDUI tizimlariga qanday foyda keltiradi?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: Mobil ilova so'rov yuborayotganda keshdagi ETag qiymatini If-None-Match sarlavhasida jo'natadi. Agar serverda ma'lumot o'zgarmagan bo'lsa, server bo'sh (0 baytlik) 304 javobini qaytaradi. Bu foydalanuvchi mobil trafigini tejaydi, batareya quvvatini sarflamaydi va serverdagi ma'lumotlar bazasi yuklamasini keskin kamaytiradi. </details>
6. Amaliy Topshiriq (Senior Challenge)
O'z loyihangizda SDUIDataEngine actorini yarating. 10 000 ta obyektdan iborat sintetik JSON ma'lumotini avval Main Threadda, so'ngra actor orqali fonda pars qilib ko'ring. CFAbsoluteTimeGetCurrent() orqali vaqtlarni solishtiring va konsolda natijani qayd eting.