- Published on
Repository pattern — data qatlami
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Repository Pattern — ViewModel ni ma'lumot manbasining tafsilotlaridan ajratadi. ViewModel "maqolalarni ber" deydi — Repository tarmoqdan mi, keshdan mi, bazadan mi — o'zi hal qiladi.
Repository tuzilmasi
View → ViewModel → Repository → DataSource(lar)
│
┌─────┴─────┐
▼ ▼
RemoteDataSource LocalDataSource
(API / URLSession) (CoreData / UserDefaults)
To'liq misol
// ═══════════════════════════════════════════════════════════════
// 📦 MODEL — sof ma'lumot
//
// Model — biznes ob'ekt. Mahsulot haqidagi ma'lumot.
// Qayerda saqlanganini bilmaydi (server, kesh, bazasi).
// Bu "entity" deb ham ataladi — domain ning asosi.
// ═══════════════════════════════════════════════════════════════
struct Mahsulot: Identifiable, Codable {
let id: Int // Noyob identifikator
var nomi: String // Mahsulot nomi
var narxi: Double // Narxi (so'm)
var tavsifi: String // Tavsif matni
}
// ═══════════════════════════════════════════════════════════════
// 📋 REPOSITORY PROTOKOL — "shartnoma"
//
// Bu shartnoma ViewModel ga aytadi:
// "Men senga mahsulotlarni bera olaman, saqlashim mumkin,
// o'chirishim mumkin. QANDAY qilishim — seni qiziqtirmasin."
//
// ViewModel FAQAT shu protokolni biladi:
// - Tarmoqdan kelganmi? — Bilmaydi ✅
// - Keshdan kelganmi? — Bilmaydi ✅
// - CoreData danmi? — Bilmaydi ✅
//
// Bu "Abstraktsiya" — murakkablikni yashirish
// ═══════════════════════════════════════════════════════════════
protocol MahsulotRepositoryProtocol {
/// Barcha mahsulotlarni olish — manba noma'lum
func hammasi() async throws -> [Mahsulot]
/// Bitta mahsulotni ID bo'yicha olish
func bittasi(id: Int) async throws -> Mahsulot
/// Yangi mahsulot saqlash yoki mavjudni yangilash
func saqlash(_ mahsulot: Mahsulot) async throws
/// Mahsulotni o'chirish
func ochirish(id: Int) async throws
}
// ═══════════════════════════════════════════════════════════════
// 🔄 HAQIQIY REPOSITORY — tarmoq + lokal kesh
//
// Bu class — ikki ma'lumot manbasi (DataSource) ni birlashtiradi:
// 1. remoteDataSource — API (server bilan aloqa)
// 2. localDataSource — lokal saqlash (CoreData, UserDefaults)
//
// Offline-first strategiya:
// ┌─────────────────────────────────────────┐
// │ 1. Tarmoqdan yangi ma'lumot SO'RASH │
// │ 2. Muvaffaqiyatli → lokal ga SAQLASH │
// │ 3. Tarmoq xato → lokal dan O'QISH │
// │ 4. Lokal ham bo'sh → XATO ko'rsatish │
// └─────────────────────────────────────────┘
//
// Foydalanuvchi internet bo'lmasa ham ilovani ishlatadi!
// ═══════════════════════════════════════════════════════════════
class MahsulotRepository: MahsulotRepositoryProtocol {
// DataSource lar — private, tashqaridan ko'rinmaydi
// Protokol turi — aniq class emas (mock almashish uchun)
private let remoteDataSource: MahsulotAPIProtocol
private let localDataSource: MahsulotStorageProtocol
// Dependency Injection — tashqaridan DataSource uzatish
// Default qiymatlar bor — oddiy ishlatishda hech narsa yozmaysiz
// Test da: MockAPI, MockStorage uzatishingiz mumkin
init(
remote: MahsulotAPIProtocol = MahsulotAPI(),
local: MahsulotStorageProtocol = MahsulotStorage()
) {
self.remoteDataSource = remote
self.localDataSource = local
}
// ── HAMMASI — offline-first strategiya ──
func hammasi() async throws -> [Mahsulot] {
// Offline-first — foydalanuvchi tajribasi uchun eng yaxshi
// Internet bo'lsa → yangi ma'lumot
// Internet bo'lmasa → oxirgi saqlangan ma'lumot
do {
// 1-qadam: Tarmoqdan yangi ma'lumot olish
let tarmoqdan = try await remoteDataSource.mahsulotlarniYukla()
// 2-qadam: Lokal ga saqlash (keyingi safar uchun kesh)
// Agar tarmoq ishlamasa — bu kesh dan o'qiymiz
try await localDataSource.saqlash(tarmoqdan)
// 3-qadam: Yangi ma'lumotni qaytarish
return tarmoqdan
} catch {
// Tarmoq xatosi! Internet yo'q yoki server ishlamayapti
// Lokal keshdan o'qishga harakat qilamiz
let lokal = try await localDataSource.hammasi()
if lokal.isEmpty {
// Lokal ham bo'sh — hech qanday ma'lumot yo'q
// Xatoni yuqoriga uzatamiz — ViewModel hal qiladi
throw error
}
// Lokal da ma'lumot bor — uni ko'rsatamiz
// Foydalanuvchi eski ma'lumotni ko'radi, lekin ilova ishlamaganidan yaxshi!
return lokal
}
}
func bittasi(id: Int) async throws -> Mahsulot {
// Bitta mahsulot — to'g'ridan-to'g'ri tarmoqdan
try await remoteDataSource.mahsulotniYukla(id: id)
}
func saqlash(_ mahsulot: Mahsulot) async throws {
// Avval serverga saqlash — u "haqiqat manbai"
try await remoteDataSource.mahsulotniSaqlash(mahsulot)
// Keyin lokal keshni yangilash
try await localDataSource.saqlash([mahsulot])
}
func ochirish(id: Int) async throws {
// Avval serverdan o'chirish
try await remoteDataSource.mahsulotniOchirish(id: id)
// Keyin lokal dan ham o'chirish — sinxron saqlash
try await localDataSource.ochirish(id: id)
}
}
// ═══════════════════════════════════════════════════════════════
// 🌐 DATA SOURCE PROTOKOLLAR
//
// Har DataSource o'z ishini qiladi:
// • MahsulotAPIProtocol — faqat tarmoq (URLSession)
// • MahsulotStorageProtocol — faqat lokal saqlash (CoreData)
//
// Repository bu ikkalasini birlashtiradi
// ViewModel faqat Repository ni biladi
// ═══════════════════════════════════════════════════════════════
protocol MahsulotAPIProtocol {
func mahsulotlarniYukla() async throws -> [Mahsulot]
func mahsulotniYukla(id: Int) async throws -> Mahsulot
func mahsulotniSaqlash(_ mahsulot: Mahsulot) async throws
func mahsulotniOchirish(id: Int) async throws
}
protocol MahsulotStorageProtocol {
func hammasi() async throws -> [Mahsulot]
func saqlash(_ mahsulotlar: [Mahsulot]) async throws
func ochirish(id: Int) async throws
}
// ═══════════════════════════════════════════════════════════════
// 🧠 VIEWMODEL — faqat Repository ni biladi
//
// ViewModel tarmoq, kesh, bazasi haqida HECH NARSA bilmaydi!
// U faqat: "repository.hammasi() ber" deydi.
// Qayerdan kelgani — Repository ning ishi.
//
// Bu ajratish (separation) test yozishni JUDA osonlashtiradi:
// Test da MockRepository uzatsak — tarmoq kerak emas!
// ═══════════════════════════════════════════════════════════════
@MainActor
class MahsulotlarViewModel: ObservableObject {
@Published var mahsulotlar: [Mahsulot] = []
@Published var yuklanmoqda = false // ProgressView uchun
@Published var xatoXabari: String? // Xato bo'lsa — nil emas
// Repository — PROTOKOL turi! Aniq class emas!
// Bu DI (Dependency Injection) ning kaliti
private let repository: MahsulotRepositoryProtocol
// Init da repository qabul qilish
// Default: haqiqiy MahsulotRepository
// Test da: MockMahsulotRepository uzatiladi
init(repository: MahsulotRepositoryProtocol = MahsulotRepository()) {
self.repository = repository
}
func yuklash() async {
yuklanmoqda = true // Spinner ko'rsatish
xatoXabari = nil // Avvalgi xatoni tozalash
do {
// ViewModel bilmaydi: tarmoqdan mi, keshdan mi?
// Repository o'zi hal qiladi!
mahsulotlar = try await repository.hammasi()
} catch {
// Xato — foydalanuvchiga xabar ko'rsatish
xatoXabari = error.localizedDescription
}
yuklanmoqda = false // Spinner yashirish
}
}
Repositorysiz vs Repository bilan
❌ Repositorysiz (ViewModel to'g'ridan-to'g'ri tarmoq):
ViewModel → URLSession → API
ViewModel → CoreData
ViewModel → UserDefaults
(ViewModel hamma narsani biladi — test qiyin)
✅ Repository bilan:
ViewModel → Repository (protokol)
│
┌─────┴─────┐
API CoreData
(ViewModel faqat protokolni biladi — test oson)
🎯 Topshiriq: Repository qo'shish
Oldingi MVVM loyihangizga Repository qatlami qo'shing. MaqolaRepositoryProtocol yarating. Haqiqiy va Mock versiyasini yozing. ViewModel ni repository init da qabul qiladigan qilib o'zgartiring. Test da MockRepository ishlatib ViewModel ni tekshiring.