🏗️ SwiftUI 的状态之上:数据流与架构(MVVM in SwiftUI)
"在状态之上,组织思想;在数据之上,构建架构。"
一、从状态到架构:为什么需要 MVVM
在前两篇中,我们认识了 SwiftUI 的核心哲学:
- 声明式 UI ------ 描述"我想要什么";
- 状态驱动 ------ "UI = f(State)"。
这些理念在小型页面中非常高效。
但当项目复杂起来,比如多页跳转、网络请求、权限控制时,
你会发现仅靠 @State
和 @ObservedObject
还不够。
这时候,我们需要更高层次的组织方式 ------ MVVM 架构。
二、什么是 MVVM?
MVVM 是 Model-View-ViewModel 的缩写,是 SwiftUI 官方推荐的架构。
层级 | 作用 | 在 SwiftUI 中的体现 |
---|---|---|
Model | 数据层(结构体/类) | 普通数据类型、网络返回结果 |
ViewModel | 状态管理层,业务逻辑 | ObservableObject + @Published |
View | 视图层(UI) | SwiftUI View 结构体 |
三、为什么 SwiftUI 天然适合 MVVM?
UIKit 时代,ViewController 又管 UI 又管逻辑,是"大杂烩"。
SwiftUI 把 UI 从控制器中完全剥离,让 View
成为纯函数式结构。
因此:
- View 只负责展示;
- ViewModel 负责状态和逻辑;
- Model 负责数据。
三者职责分明、天然分层。
四、MVVM 结构图
+----------------------+
| View |
| SwiftUI 描述 UI |
+----------+-----------+
|
@ObservedObject
|
+----------v-----------+
| ViewModel |
| 管理状态 + 业务逻辑 |
| ObservableObject |
+----------+-----------+
|
调用网络层
|
+----------v-----------+
| Model |
| 数据结构 + 请求响应 |
+----------------------+
五、从一个 To-Do App 看 MVVM 的落地
我们以一个简单的待办事项应用为例。
🧱 Model:任务数据结构
swift
struct Task: Identifiable, Codable {
let id = UUID()
var title: String
var isDone: Bool = false
}
⚙️ ViewModel:任务逻辑与状态管理
swift
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
func addTask(title: String) {
guard !title.isEmpty else { return }
tasks.append(Task(title: title))
}
func toggleDone(task: Task) {
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks[index].isDone.toggle()
}
}
func removeTask(at offsets: IndexSet) {
tasks.remove(atOffsets: offsets)
}
}
🪟 View:声明式界面展示
swift
struct TaskListView: View {
@StateObject private var viewModel = TaskViewModel()
@State private var newTask = ""
var body: some View {
NavigationView {
VStack {
List {
ForEach(viewModel.tasks) { task in
HStack {
Image(systemName: task.isDone ? "checkmark.circle.fill" : "circle")
.onTapGesture { viewModel.toggleDone(task: task) }
Text(task.title)
.strikethrough(task.isDone)
}
}
.onDelete(perform: viewModel.removeTask)
}
HStack {
TextField("New Task", text: $newTask)
Button("Add") {
viewModel.addTask(title: newTask)
newTask = ""
}
}
.padding()
}
.navigationTitle("My Tasks")
}
}
}
六、SwiftUI MVVM 的关键要点
概念 | SwiftUI 实现 | 说明 |
---|---|---|
数据绑定 | @ObservedObject 、@StateObject |
View 与 ViewModel 双向绑定 |
自动刷新 | @Published |
状态更新时自动刷新 UI |
单向数据流 | ViewModel → View | 数据从上而下传递 |
无副作用 UI | View 只描述布局,不管理逻辑 | 视图是"结果",非"命令" |
七、MVVM + SwiftUI 的典型组合
模块 | 说明 | 实现方式 |
---|---|---|
View | 负责界面展示 | struct + body |
ViewModel | 业务逻辑 + 状态 | ObservableObject |
Model | 数据结构 | struct |
网络层 | 数据请求 | async/await + URLSession |
存储层 | 本地数据 | UserDefaults / CoreData |
全局状态 | 环境对象 | @EnvironmentObject |
八、实际项目中的 MVVM 分层建议
当你项目越来越复杂,可以这样组织目录:
📁 ProjectName
┣ 📂 Models
┃ ┗ Task.swift
┣ 📂 ViewModels
┃ ┗ TaskViewModel.swift
┣ 📂 Views
┃ ┗ TaskListView.swift
┣ 📂 Services
┃ ┗ NetworkManager.swift
┣ 📂 Resources
┃ ┗ Assets.xcassets
┗ ProjectNameApp.swift
九、再往上一步:MVVM + Combine + async/await
在大型项目中,ViewModel 还经常结合 Combine 框架或异步函数。
swift
@MainActor
class NewsViewModel: ObservableObject {
@Published var articles: [Article] = []
func fetchNews() async {
do {
let url = URL(string: "https://newsapi.org/...")!
let (data, _) = try await URLSession.shared.data(from: url)
let decoded = try JSONDecoder().decode([Article].self, from: data)
articles = decoded
} catch {
print("Error:", error)
}
}
}
SwiftUI 与
async/await
完美融合,让"声明式 UI + 异步状态"成为一种自然写法。
十、结语:SwiftUI 架构的三重境界
1️⃣ 声明式 UI
"我描述界面,不命令界面。"
2️⃣ 状态驱动
"UI = f(State)。"
3️⃣ 架构思维(MVVM)
"状态有归属,逻辑有层次。"
当你理解这三层关系时,
SwiftUI 就不再只是语法糖,
而是一个完整、优雅、可扩展的 UI 哲学体系。
从命令到声明,从变量到状态,从函数到架构。
这就是 SwiftUI。