14. Repository Pattern va Offline-First: SQLite + Stale-While-Revalidate
🏛️ iOS Design Patterns — Mukammal Enterprise & Architecture Qo‘llanma
14. Apple Native: Repository Pattern va Offline-First Sinxronizatsiyasi
"Agar sizning ViewModel'ingiz ichida ham 'URLSession.shared.data', ham 'CoreData.fetchRequest' yozilgan bo'lsa — siz ma'lumotlar boshqaruvi tartibini buzdingiz. Repository — bu ilovaning Yagona Haqiqat Manbaidir (Single Source of Truth)."
1. Repository Pattern Nima va Uning Asosiy Vazifasi?
Repository (Omborxona) — bu domen va biznes mantiqi (ViewModel/UseCase) bilan ma'lumotlarni saqlovchi quyi qatlamlar (Local SQLite DB, Remote REST API, Kesh) o'rtasidagi vositachi andozadir.
graph TD
ViewModel["ProductListViewModel"] --> Repo["ProductRepository (Yagona Haqiqat Manbai)"]
Repo --> Local["Mahalliy Ombor (SQLite / CoreData / SwiftData)"]
Repo --> Remote["Masofaviy Server (REST / gRPC API)"]
Local -.->|"0 ms: Keshdan darhol ko'rsatish"| Repo
Remote -.->|"Fonda yangi data kelganda DB ni yangilash"| Local
style ViewModel fill:#181d26,stroke:#7da2ce,stroke-width:1px
style Repo fill:#131720,stroke:#bef264,stroke-width:2px
style Local fill:#181d26,stroke:#60a5fa,stroke-width:1px
style Remote fill:#181d26,stroke:#60a5fa,stroke-width:1px
Oltin Qoida:
ViewModel ma'lumot internetdan keldimi yoki diskdagi SQLite'dan keldimi — buni bilmasligi kerak. U faqat Repository'dan getProducts() deb so'raydi.
2. Offline-First (Offline-Birinchi) Sinxronizatsiya Strategiyasi
Foydalanuvchi ilovani ochganda hech qachon aylanayotgan kutish belgisini (Spinner) ko'rib 3 soniya kutib turmasligi lozim:
sequenceDiagram
autonumber
participant UI as ViewModel (UI)
participant Repo as ProductRepository
participant DB as Local Database (SQLite)
participant API as Remote Server API
UI->>Repo: getProducts()
Repo->>DB: fetchCachedProducts()
DB-->>Repo: [Mahsulotlar (Kesh)]
Repo-->>UI: 0 ms: Darhol ekranga chiqarish! (Instant UI)
Repo->>API: Fonda: GET /api/v1/products
API-->>Repo: 200 OK + Yangi narxlar
Repo->>DB: save(newProducts) (Mahalliy bazani yangilash)
DB-->>Repo: Yangilanish hodisasi
Repo-->>UI: Faqat o'zgargan narxlar ekranda yangilanadi!
3. Real Swift 6 Kodi: Enterprise ProductRepository
import Foundation
// 1. Domen Modeli (Sof Swift struktura)
public struct Product: Identifiable, Codable, Sendable {
public let id: String
public let title: String
public let price: Double
}
// 2. Repository Protokoli (Kontrakt)
public protocol ProductRepositoryProtocol: Sendable {
func getProducts() async throws -> [Product]
func syncWithServer() async throws
}
// 3. Repository Implementatsiyasi
public final class ProductRepository: ProductRepositoryProtocol {
private let localStore: LocalProductStorage
private let remoteAPI: RemoteProductAPI
public init(localStore: LocalProductStorage, remoteAPI: RemoteProductAPI) {
self.localStore = localStore
self.remoteAPI = remoteAPI
}
public func getProducts() async throws -> [Product] {
// 1. Avval lokal keshni tekshiramiz
let cached = try await localStore.fetchProducts()
// 2. Orqa fonda yangilash vazifasini ishga tushiramiz
Task {
try? await syncWithServer()
}
if !cached.isEmpty {
return cached
}
// 3. Agar lokal bo'sh bo'lsa, serverni kutamiz
return try await remoteAPI.fetchProducts()
}
public func syncWithServer() async throws {
let freshProducts = try await remoteAPI.fetchProducts()
try await localStore.save(freshProducts)
print("💾 Mahalliy SQLite bazasi eng so'nggi ma'lumotlar bilan yangilandi!")
}
}
// Yordamchi mahalliy va server interfeyslari
public actor LocalProductStorage {
private var cache: [Product] = []
public func fetchProducts() -> [Product] { cache }
public func save(_ products: [Product]) { self.cache = products }
}
public actor RemoteProductAPI {
public func fetchProducts() async throws -> [Product] {
// HTTP API chaqiruvi
return [Product(id: "p1", title: "MacBook Pro M4", price: 2199.0)]
}
}
4. Interaktiv Savol-Javob (Self-Check Quiz)
<details> <summary><b>1-Savol: Nega Repository ichida CoreData NSManagedObject obyektlarini to'g'ridan-to'g'ri ViewModel'ga qaytarish xavfli?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim: NSManagedObject o'zining NSManagedObjectContext (oqim)iga qattiq bog'langan bo'ladi. Agar siz uni boshqa oqimda (Thread) yoki SwiftUI View'da o'qishga urinsangiz, CoreData darhol ilovani qulatadi (crash: multithreading violation). Shuning uchun Repository CoreData obyektini darhol sof Swift structiga (Product) o'girib (mapping qilib), xavfsiz holda qaytarishi shart! </details>
<details> <summary><b>2-Savol: Repository va DAO (Data Access Object) o'rtasida qanday farq bor?</b> (Yechimni ko'rish uchun bosing)</summary>
Tahlil va Yechim:
- DAO — bu faqat bitta aniq ma'lumotlar bazasi jadvali bilan ishlovchi pastki darajadagi obyektdir (masalan,
UserTableDAOfaqat SQLSELECT/INSERTqiladi). - Repository — bu yuqoriroq darajadagi domen andozasi. U o'z ichida ham DAOni, ham tarmoq API sini birlashtirib, bitta mantiqiy haqiqat manbaini taqdim etadi.
</details>
5. Amaliy Topshiriq (Senior Challenge)
UserRepository sinfini yarating:
- Foydalanuvchi profilini avval
UserDefaultskeshidan o'qisin. - So'ngra serverdan yangi profilni olib keshni yangilasun.
- Agar internet bo'lmasa, keshdagi eski ma'lumotni muammosiz ko'rsatishda davom etsin!