Azimjon Abdurasulov

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, UserTableDAO faqat SQL SELECT/INSERT qiladi).
  • 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 UserDefaults keshidan o'qisin.
  • So'ngra serverdan yangi profilni olib keshni yangilasun.
  • Agar internet bo'lmasa, keshdagi eski ma'lumotni muammosiz ko'rsatishda davom etsin!

Markdown manba

iOS Lessons