MVVM 与架构
Clean Architecture、依赖注入。
30 分钟
MVVM 与架构
SwiftUI 项目推荐 MVVM + Repository:View 薄、ViewModel 编排业务、Repository 抽象数据源。对照 /roadmaps/swift-ui「架构」节点,清晰分层便于测试与扩展。
学习目标
- 理解 MVVM 在 SwiftUI 中的职责划分
- 用 Protocol 定义 Repository,支持依赖注入
- 了解 Clean Architecture 分层思想
- 组织可维护的项目目录结构
核心概念
MVVM 分层
// Model
struct Track: Identifiable, Codable, Equatable {
let id: Int
var title: String
var lessonCount: Int
}
// Repository 协议
protocol TrackRepositoryProtocol {
func fetchAll() async throws -> [Track]
}
// 实现:可替换为网络 / 本地
final class TrackRepository: TrackRepositoryProtocol {
func fetchAll() async throws -> [Track] {
try await Task.sleep(nanoseconds: 300_000_000)
return [
Track(id: 1, title: "SwiftUI 入门", lessonCount: 12),
Track(id: 2, title: "React 对照", lessonCount: 10),
]
}
}
// ViewModel
@MainActor
final class TrackListViewModel: ObservableObject {
@Published var tracks: [Track] = []
@Published var isLoading = false
@Published var errorMessage: String?
private let repository: TrackRepositoryProtocol
init(repository: TrackRepositoryProtocol = TrackRepository()) {
self.repository = repository
}
func load() async {
isLoading = true
errorMessage = nil
do {
tracks = try await repository.fetchAll()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
// View:只负责 UI
struct TrackListScreen: View {
@StateObject private var viewModel = TrackListViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else {
List(viewModel.tracks) { track in
Text("\(track.title)(\(track.lessonCount) 课)")
}
}
}
.task { await viewModel.load() }
.alert("错误", isPresented: .constant(viewModel.errorMessage != nil)) {
Button("确定") { viewModel.errorMessage = nil }
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}
View 不含业务逻辑;ViewModel 通过 Protocol 注入 Repository,单元测试可传入 Mock。
项目结构
SwiftUIApp/
├── App/
│ └── SwiftUIAppApp.swift
├── Models/
│ └── Track.swift
├── ViewModels/
│ └── TrackListViewModel.swift
├── Views/
│ └── TrackListScreen.swift
├── Services/
│ └── TrackRepository.swift
└── Tests/
└── TrackListViewModelTests.swift