Published on

SwiftUI-da Core Data va MVVM arxitekturasi

Authors

Avvalgi videoda Core Data-ni @FetchRequest orqali to'g'ridan-to'g'ri View ichida ishlatdik. Bu boshlang'ich uchun qulay, lekin production ilovalar uchun tavsiya etilmaydi — View ichida juda ko'p logika to'planib qoladi. Bu videoda xuddi shu Core Data logikasi MVVM arxitekturasi bilan qayta yoziladi: barcha logika ViewModel-ga ko'chiriladi, View faqat UI bilan shug'ullanadi.


@FetchRequest vs MVVM

Avvalgi video (@FetchRequest):
View = UI + @FetchRequest + addItem + deleteItem + saveItems

Bu video (MVVM):
View = faqat UI
CoreDataViewModel = NSPersistentContainer + fetchFruits + addFruit + deleteFruit + updateFruit + saveData

1-qadam: FruitsContainer yaratish

Navigator → yangi fayl → Core Data bo'limida Data Model → nom: FruitsContainer

Yaratilgan faylda:

  1. Add EntityFruitEntity
  2. Add Attributename, turi String

Container nomi (FruitsContainer) keyinchalik NSPersistentContainer-da ishlatiladi — aynan bir xil yozilishi shart.


2-qadam: CoreDataViewModel

import CoreData

class CoreDataViewModel: ObservableObject {

    let container: NSPersistentContainer

    @Published var savedEntities: [FruitEntity] = []

    init() {
        // Container nomi = FruitsContainer.xcdatamodeld fayl nomi
        container = NSPersistentContainer(name: "FruitsContainer")

        container.loadPersistentStores { _, error in
            if let error = error {
                print("Core Data yuklanmadi: \(error)")
            } else {
                print("Core Data muvaffaqiyatli yuklandi")
            }
        }

        fetchFruits()
    }

    // MARK: - Fetch

    func fetchFruits() {
        // @FetchRequest o'rniga — ViewModel ichida NSFetchRequest
        let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")
        // entityName = .xcdatamodeld dagi entity nomi bilan aynan bir xil

        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch {
            print("Fetch xatosi: \(error)")
        }
    }

    // MARK: - Add

    func addFruit(text: String) {
        let newFruit = FruitEntity(context: container.viewContext)
        newFruit.name = text
        saveData()
    }

    // MARK: - Delete

    func deleteFruit(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedEntities[index]
        container.viewContext.delete(entity)
        saveData()
    }

    // MARK: - Update

    func updateFruit(entity: FruitEntity) {
        let currentName = entity.name ?? ""
        let newName = currentName + "!"
        entity.name = newName
        saveData()
    }

    // MARK: - Save

    func saveData() {
        do {
            try container.viewContext.save()
            // Saqlangandan keyin yangi ma'lumotlarni qayta yuklash
            // @FetchRequest-dan farqi: bu qo'lda bajarilishi kerak
            fetchFruits()
        } catch {
            print("Saqlash xatosi: \(error)")
        }
    }
}

Muhim: @FetchRequest ishlatilganda ma'lumot saqlanganda view avtomatik yangilanardi. MVVM-da esa saveData() ichida fetchFruits() qo'lda chaqirilishi kerak — aks holda savedEntities yangilanmaydi va ekran o'zgarmaydi.


3-qadam: View — faqat UI

struct CoreDataBootcamp: View {

    @StateObject var vm: CoreDataViewModel = CoreDataViewModel()
    @State private var textFieldText: String = ""

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {

                // Kiritish maydoni
                TextField("Meva nomini kiriting...", text: $textFieldText)
                    .font(.headline)
                    .padding(.leading)
                    .frame(height: 55)
                    .frame(maxWidth: .infinity)
                    .background(Color(.systemGray5))
                    .cornerRadius(10)
                    .padding(.horizontal)

                // Qo'shish tugmasi
                Button {
                    guard !textFieldText.isEmpty else { return }
                    vm.addFruit(text: textFieldText)
                    textFieldText = ""
                } label: {
                    Text("Saqlash")
                        .font(.headline)
                        .foregroundColor(.white)
                        .frame(height: 55)
                        .frame(maxWidth: .infinity)
                        .background(Color.pink)
                        .cornerRadius(10)
                }
                .padding(.horizontal)

                // Mevalar ro'yxati
                List {
                    ForEach(vm.savedEntities) { entity in
                        Text(entity.name ?? "Nomsiz")
                            .onTapGesture {
                                vm.updateFruit(entity: entity)
                            }
                    }
                    .onDelete(perform: vm.deleteFruit)
                }
                .listStyle(PlainListStyle())
            }
            .navigationTitle("Mevalar")
        }
    }
}

View-da hech qanday Core Data logikasi yo'q — faqat vm.addFruit, vm.deleteFruit, vm.updateFruit chaqiriladi.


NSFetchRequest nozikliklari

Entity nomi

// ✅ TO'G'RI — .xcdatamodeld dagi entity nomi bilan aynan mos
let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")

// ❌ XATO — katta-kichik harf farqiga e'tibor bering
let request = NSFetchRequest<FruitEntity>(entityName: "fruitEntity")
let request = NSFetchRequest<FruitEntity>(entityName: "Fruit_Entity")

Agar NSPersistentContainer nomi yoki NSFetchRequest-dagi entity nomi .xcdatamodeld fayli va entity nomidan farq qilsa — Core Data yuklanmaydi. Xato "error loading core data" xabari sifatida ko'rinadi.

Xcode qayta ishga tushirish

Yangi entity yaratilganda Xcode ba'zan uni darhol taniy olmaydi — "cannot find FruitEntity in scope" xatosi beradi. Bu holda Xcode-ni yopib qayta ochish (Command+Q, keyin qayta ochish) muammoni hal qiladi.

Saralash qo'shish

func fetchFruits() {
    let request = NSFetchRequest<FruitEntity>(entityName: "FruitEntity")

    // A → Z tartibda saralash
    let sort = NSSortDescriptor(keyPath: \FruitEntity.name, ascending: true)
    request.sortDescriptors = [sort]

    do {
        savedEntities = try container.viewContext.fetch(request)
    } catch {
        print("Fetch xatosi: \(error)")
    }
}

@FetchRequest vs MVVM — qaysi yaxshiroq?

@FetchRequestMVVM
Kod miqdoriKamKo'proq
O'rganish qulayligiOsonroqBiroz murakkabroq
View da logikaBorYo'q
KengaytirishQiyinOson
Production uchunKam tavsiyaTavsiya etiladi
Avtomatik yangilanishHa (@FetchRequest)Qo'lda (fetchFruits qayta chaqiriladi)

Kichik loyihalar uchun @FetchRequest yetarli. Katta va professional ilovalarda MVVM ancha samarali — View faqat UI bilan shug'ullanadi, ViewModel barcha data logikasini boshqaradi.


Xulosa — MVVM bilan Core Data asosiy qadamlari

1. .xcdatamodeld → entity va atributlar yaratish
2. CoreDataViewModel:
   - NSPersistentContainer (container nomi = .xcdatamodeld fayl nomi)
   - loadPersistentStores + fetchFruits (init ichida)
   - @Published savedEntities: [FruitEntity]
   - fetchFruits()NSFetchRequest bilan
   - addFruit(text:) — yangi entity + saveData()
   - deleteFruit(indexSet:) — entity o'chirish + saveData()
   - updateFruit(entity:) — entity yangilash + saveData()
   - saveData() — viewContext.save() + fetchFruits()
3. View:
   - @StateObject var vm = CoreDataViewModel()
   - TextField + Button → vm.addFruit()
   - List + ForEach(vm.savedEntities) → onDelete + onTapGesture

Core Data-dagi har bir o'zgarish (qo'shish, o'chirish, yangilash) uchun saveData() chaqirilishi shart — aks holda o'zgarishlar saqlana olmaydi.

Buy mea coffee