- Published on
SwiftUI-da ObservableObject va StateObject — MVVM arxitekturasi
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
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
| Ishlatilishi | Qayerda | |
|---|---|---|
@State | Oddiy o'zgaruvchi kuzatuvi | View ichida |
@Published | O'zgaruvchi kuzatuvi | Class (ViewModel) ichida |
@StateObject | ViewModel-ni birinchi marta yaratish | Birinchi View-da |
@ObservedObject | ViewModel-ni qabul qilish | Ikkinchi 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
@Published — ViewModel 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!