معماری MVVM (MVVM Architecture)
اینجا معماری «MVVM» را ساده می چینیم. «MVVM» یعنی Model-View-ViewModel. هدف، جداسازی نمایش و منطق است. بنابراین کدها تمیزتر و تست پذیرتر می شوند. مثل مدرسه، معلم توضیح می دهد و شاگرد فقط می بیند.
الگوی Model-View-ViewModel
در «MVVM»، View فقط UI است. ViewModel منطق و وضعیت را نگه می دارد. Model هم داده خام است. «ObservableObject» یعنی شی ء قابل اعلان تغییر. «@Published» یعنی اعلام تغییر مقدار. «@StateObject» یعنی منبع حقیقت در View.
نمونه پایه MVVM
در این نمونه، ViewModel یک لیست وظیفه می دهد. View آن را نمایش می دهد. سپس در ظاهر شدن، داده بارگذاری می شود.
import SwiftUI
struct MVVMBasicView: View {
@StateObject private var vm: TodoViewModel = TodoViewModel()
var body: some View {
List(vm.todos) { t in
Text(t.title)
}
.onAppear {
vm.load()
}
}
}
import Foundation
import Combine
struct Todo: Identifiable {
let id: Int
let title: String
}
final class TodoViewModel: ObservableObject {
@Published var todos: [Todo] = []
func load() {
todos = [
Todo(id: 1, title: "Learn MVVM")
]
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
MVVMBasicView()
}
}
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
نمونه Notes با MVVM
حالا یک مدل Note می سازیم. سپس ViewModel لیست را می سازد. در پایان، View آن را نشان می دهد.
import SwiftUI
struct NotesMVVMView: View {
@StateObject private var vm: NotesViewModel = NotesViewModel()
var body: some View {
List(vm.notes) { n in
VStack(alignment: .leading) {
Text(n.title).font(.headline)
Text(n.body).font(.subheadline)
}
}
.onAppear {
vm.loadMock()
}
}
}
import Foundation
import Combine
struct Note: Identifiable {
let id: UUID
var title: String
var body: String
}
final class NotesViewModel: ObservableObject {
@Published var notes: [Note] = []
func loadMock() {
notes = [
Note(id: UUID(), title: "First", body: "Welcome to Notes"),
Note(id: UUID(), title: "Second", body: "Try editing...")
]
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
NotesMVVMView()
}
}
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
هوک App Group برای شمارش
می توانی تعداد نوت ها را در App Group ذخیره کنی. سپس ویجت همان مقدار را می خواند. این هماهنگی UI را بهتر می کند.
import Combine
func updateSharedNotesCount(_ count: Int) {
let defaults: UserDefaults? = UserDefaults(suiteName: "group.com.example.notes")
defaults?.set(count, forKey: "notesCount")
}
class NotesViewModel: ObservableObject {
@Published var notes: [Note] = [] {
didSet {
updateSharedNotesCount(notes.count)
}
}
func add(title: String, body: String) {
notes.append(
Note(id: UUID(), title: title, body: body)
)
}
func delete(at offsets: IndexSet) {
notes.remove(atOffsets: offsets)
}
}
import SwiftUI
// خواندن مستقیم با UserDefaults
let defaults: UserDefaults? = UserDefaults(suiteName: "group.com.example.notes")
let count: Int = defaults?.integer(forKey: "notesCount") ?? 0
// خواندن با @AppStorage در ویجت
struct WidgetCountView: View {
@AppStorage(
"notesCount",
store: UserDefaults(suiteName: "group.com.example.notes")
)
private var notesCount: Int = 0
var body: some View {
Text("Notes: \(notesCount)")
}
}
جمع بندی سریع
- View فقط نمایش می دهد.
- ViewModel منطق و وضعیت است.
- @Published تغییرات را مخابره می کند.
- @StateObject منبع حقیقت View است.
- App Group داده را با ویجت هم رسان می کند.
نکته: وابستگی ها را به ViewModel تزریق کن. بنابراین تست راحت تر می شود. همچنین View ساده تر می ماند.
برای ادامه «شبکه سازی (Networking)»، به شبکه سازی با URLSession برو. برای «ماندگاری (Persistence)»، به Core Data سر بزن.