Published on

SwiftUI-da ObservableObject va StateObject — MVVM arxitekturasi

Authors

Bu bootcamp-ning eng muhim videolaridan biri. Avvalgi videolarda hamma narsani to'g'ridan-to'g'ri View ichida yozdik. Ammo haqiqiy ilovalarda bu yondashuv ishlol qilmaydi — View juda ulkan va chigal bo'lib ketadi. Bu videoda biz MVVM arxitekturasining asosini — ViewModel yaratishni o'rganamiz.


Muammo — hamma narsa View ichida

Avval shunday yozganmiz:

struct ViewModelBootcamp: View {

    @State var fruitArray: [FruitModel] = []
    @State var isLoading: Bool = false

    var body: some View {
        NavigationView {
            List {
                ForEach(fruitArray) { fruit in
                    HStack {
                        Text("\(fruit.count)")
                            .foregroundColor(.red)
                            .bold()
                        Text(fruit.name)
                            .font(.headline)
                    }
                }
                .onDelete(perform: deleteFruit)
            }
            .listStyle(GroupedListStyle())
            .navigationTitle("Mevalar ro'yxati")
            .onAppear {
                getFruits()
            }
        }
    }

    // Bu funksiyalar View ichida — noto'g'ri amaliyot
    func getFruits() {
        let fruit1 = FruitModel(name: "Apelsin", count: 1)
        let fruit2 = FruitModel(name: "Banan",   count: 2)
        let fruit3 = FruitModel(name: "Tarvuz",  count: 88)
        fruitArray.append(contentsOf: [fruit1, fruit2, fruit3])
    }

    func deleteFruit(index: IndexSet) {
        fruitArray.remove(atOffsets: index)
    }
}

Bu ishlaydi, lekin muammo bor: getFruits va deleteFruit ma'lumotlar bilan ishlaydi — View-ning ishi bu emas. View faqat ko'rinish uchun mas'ul bo'lishi kerak.


Model yaratish

struct FruitModel: Identifiable {
    let id: String = UUID().uuidString
    let name: String
    let count: Int
}

ViewModel yaratish — logikani ajratish

Ma'lumotlar bilan bog'liq barcha kodni alohida class-ga ko'chiramiz:

class FruitViewModel: ObservableObject {

    // @Published — @State ning class versiyasi
    // Bu o'zgaruvchi o'zgarganda barcha kuzatuvchilar xabar oladi
    @Published var fruitArray: [FruitModel] = []
    @Published var isLoading: Bool = false

    // init — class birinchi marta yaratilganda ishlaydi
    init() {
        getFruits()
    }

    func getFruits() {
        isLoading = true

        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            let fruit1 = FruitModel(name: "Apelsin", count: 1)
            let fruit2 = FruitModel(name: "Banan",   count: 2)
            let fruit3 = FruitModel(name: "Tarvuz",  count: 88)

            self.fruitArray.append(contentsOf: [fruit1, fruit2, fruit3])
            self.isLoading = false
            // DispatchQueue closure ichida self yozish shart
        }
    }

    func deleteFruit(index: IndexSet) {
        fruitArray.remove(atOffsets: index)
    }
}

ObservableObject — bu protokol. Uni qo'llash Xcode-ga: "Bu class-ni kuzatish mumkin, ichidagi o'zgaruvchilar o'zgarsa View-larni yangilang" deydi.

@Published — Class ichidagi @State. Bu o'zgaruvchi o'zgarganda barcha kuzatuvchi View-lar avtomatik yangilanadi.


View-ni soddalashtirish

Endi View faqat ko'rinish bilan shug'ullanadi:

struct ViewModelBootcamp: View {

    // @StateObject — ViewModel-ni birinchi marta yaratish uchun
    @StateObject var fruitViewModel: FruitViewModel = FruitViewModel()

    var body: some View {
        NavigationView {
            List {
                if fruitViewModel.isLoading {
                    ProgressView()  // yuklash indikatori
                } else {
                    ForEach(fruitViewModel.fruitArray) { fruit in
                        HStack {
                            Text("\(fruit.count)")
                                .foregroundColor(.red)
                                .bold()
                            Text(fruit.name)
                                .font(.headline)
                        }
                    }
                    .onDelete(perform: fruitViewModel.deleteFruit)
                }
            }
            .listStyle(GroupedListStyle())
            .navigationTitle("Mevalar ro'yxati")
        }
    }
}

View endi: ma'lumotlarni olib kelish, o'chirish logikasi yo'q. Faqat ko'rsatish. Barcha mantiq FruitViewModel-da.


@ObservedObject vs @StateObject

Bu ikkalasi juda o'xshash, lekin muhim farqi bor:

// @StateObject — birinchi marta yaratishda ishlating
// View qayta yuklananda ViewModel SAQLANIB QOLADI
@StateObject var fruitViewModel: FruitViewModel = FruitViewModel()

// @ObservedObject — ikkinchi va keyingi ekranlarga uzatishda ishlating
// View qayta yuklananda ViewModel ham qayta yaratiladi (xavfli)
@ObservedObject var fruitViewModel: FruitViewModel

Qoida:

  • @StateObject — ViewModel-ni birinchi marta yaratayotganda
  • @ObservedObject — ViewModel-ni boshqa ekranga uzatayotganda
// ❌ @ObservedObject muammosi:
// View qayta render bo'lsa — ViewModel ham qayta yaratiladi
// Ma'lumotlar yo'qolishi mumkin

// ✅ @StateObject to'g'ri ishlaydi:
// View qayta render bo'lsa — ViewModel saqlanib qoladi
// Ma'lumotlar o'zgarmaydi

ViewModel-ni ikkinchi ekranga uzatish

// Birinchi ekran — @StateObject bilan yaratish
struct ViewModelBootcamp: View {

    @StateObject var fruitViewModel: FruitViewModel = FruitViewModel()

    var body: some View {
        NavigationView {
            List { ... }
            .navigationBarItems(trailing:
                NavigationLink {
                    // ViewModel-ni ikkinchi ekranga uzatish
                    RandomScreen(fruitViewModel: fruitViewModel)
                } label: {
                    Image(systemName: "arrow.right")
                        .font(.title)
                }
            )
        }
    }
}

// Ikkinchi ekran — @ObservedObject bilan qabul qilish
struct RandomScreen: View {

    // Bu yerda @StateObject emas, @ObservedObject
    @ObservedObject var fruitViewModel: FruitViewModel

    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        ZStack {
            Color.green.ignoresSafeArea()

            VStack {
                ForEach(fruitViewModel.fruitArray) { fruit in
                    Text(fruit.name)
                        .foregroundColor(.white)
                        .font(.headline)
                }
            }
        }
    }
}

@Published, @StateObject, @ObservedObject — qisqacha taqqoslash

IshlatilishiQayerda
@StateOddiy o'zgaruvchi kuzatuviView ichida
@PublishedO'zgaruvchi kuzatuviClass (ViewModel) ichida
@StateObjectViewModel-ni birinchi marta yaratishBirinchi View-da
@ObservedObjectViewModel-ni qabul qilishIkkinchi va keyingi View-larda

MVVM arxitekturasi — umumiy tuzilma

// MODEL — faqat ma'lumot tuzilmasi
struct FruitModel: Identifiable {
    let id: String = UUID().uuidString
    let name: String
    let count: Int
}

// VIEWMODEL — ma'lumot logikasi
class FruitViewModel: ObservableObject {
    @Published var fruitArray: [FruitModel] = []
    @Published var isLoading: Bool = false

    func getFruits() { ... }
    func deleteFruit(index: IndexSet) { ... }
}

// VIEW — faqat ko'rinish
struct FruitView: View {
    @StateObject var viewModel = FruitViewModel()

    var body: some View {
        List {
            ForEach(viewModel.fruitArray) { fruit in
                Text(fruit.name)
            }
        }
    }
}

init bilan onAppear muammosini hal qilish

// ❌ Muammo: onAppear har safar View ko'ringanda chaqiriladi
// Ekrandan chiqib qaytganda yana ma'lumot yuklanadi
.onAppear {
    fruitViewModel.getFruits()
}

// ✅ To'g'ri: init faqat ViewModel birinchi marta yaratilganda ishlaydi
class FruitViewModel: ObservableObject {
    init() {
        getFruits()  // faqat bir marta
    }
}

Xulosa

Bu video murakkab tuyulishi mumkin — lekin asosiy g'oyani eslab qolish yetarli:

View          — faqat ko'rinish (UI)
ViewModel     — faqat ma'lumot logikasi

@PublishedViewModel ichida o'zgaruvchi kuzatuvi
@StateObject  — birinchi View-da ViewModel yaratish
@ObservedObject — keyingi View-larda ViewModel qabul qilish

Haqiqiy ilovalarda har doim shu tuzilmani ishlatasiz. Bu videoni bir marta ko'rib bo'lmasa — qayta ko'ring, chunki bundan keyingi barcha videolarda MVVM ishlatiladi.

Rahmat, men Nick, bu Swiftful Thinking va keyingi videoda ko'rishguncha!

Buy mea coffee