MVVM 项目模板与代码片段
标准 MVVM 项目结构模板
MyMVVMProject/
├── Models/ # 数据模型层
│ ├── Todo.swift # 核心数据模型
│ └── NetworkModels/ # 网络相关模型(可选)
├── ViewModels/ # 视图模型层
│ ├── TodoListViewModel.swift
│ └── ViewModelFactory.swift # 集中管理 ViewModel 创建(可选)
├── Views/ # 视图层
│ ├── Components/ # 可复用组件
│ │ └── TodoCell.swift
│ ├── Screens/
│ │ └── TodoListView.swift
│ └── StyleGuide.swift # 样式规范(可选)
├── Services/ # 服务层(网络/数据库)
│ ├── APIService.swift
│ └── StorageService.swift
├── Utilities/ # 工具类
│ ├── Extensions/
│ └── Logger.swift
└── Resources/ # 资源文件
核心代码片段
1. 基础 ViewModel 模板
import Combine
class BaseViewModel: ObservableObject {
var cancellables = Set<AnyCancellable>()
deinit {
cancellables.forEach { $0.cancel() }
}
}
class TodoListViewModel: BaseViewModel {
@Published var todos: [Todo] = []
@Published var isLoading = false
@Published var error: Error?
private let service: TodoServiceProtocol
init(service: TodoServiceProtocol = TodoService()) {
self.service = service
super.init()
loadTodos()
}
func loadTodos() {
isLoading = true
service.fetchTodos()
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.error = error
}
} receiveValue: { [weak self] todos in
self?.todos = todos
}
.store(in: &cancellables)
}
}
2. 数据绑定示例
struct TodoListView: View {
@StateObject var viewModel = TodoListViewModel()
var body: some View {
Group {
if viewModel.isLoading {
ProgressView()
} else {
List(viewModel.todos) { todo in
TodoCell(todo: todo)
}
}
}
.alert("Error",
isPresented: .constant(viewModel.error != nil),
presenting: viewModel.error) { _ in
Button("OK") { viewModel.error = nil }
} message: { error in
Text(error.localizedDescription)
}
}
}
3. 依赖注入模板
// 协议定义
protocol TodoServiceProtocol {
func fetchTodos() -> AnyPublisher<[Todo], Error>
}
// 生产环境实现
class TodoService: TodoServiceProtocol {
func fetchTodos() -> AnyPublisher<[Todo], Error> {
// 实际网络请求实现
}
}
// 测试环境 Mock
class MockTodoService: TodoServiceProtocol {
var stubTodos: [Todo] = []
var shouldFail = false
func fetchTodos() -> AnyPublisher<[Todo], Error> {
if shouldFail {
return Fail(error: NSError(domain: "test", code: 500))
.eraseToAnyPublisher()
}
return Just(stubTodos)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
实用扩展片段
1. 简化绑定的 ViewModel 扩展
extension ViewModel {
func bind<T>(
_ publisher: AnyPublisher<T, Never>,
to binding: Binding<T>
) -> AnyCancellable {
publisher
.receive(on: RunLoop.main)
.assign(to: \.wrappedValue, on: binding)
}
}
2. 调试 ObservableObject 的扩展
extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
func debugPrint(_ message: String) -> Self {
objectWillChange
.sink { _ in
print("[\(Self.self)] \(message)")
}
.store(in: &cancellables)
return self
}
}
项目模板资源
Xcode 项目模板
推荐使用 SwiftUI-MVVM-Template 快速创建项目Sourcery 代码生成
通过模板自动生成 MVVM 基础代码:# sourcery.yml templates: - MVVM/ViewModel.stencil - MVVM/View.stencil output: ViewModels/{{ name }}ViewModel.swift Views/{{ name }}View.swiftSwift Package 模块化模板
将各层拆分为独立模块:// Package.swift targets: [ .target(name: "Models"), .target(name: "ViewModels", dependencies: ["Models"]), .target(name: "Views", dependencies: ["ViewModels"]) ]
提示:所有代码片段都遵循 SwiftUI 3.0+ 和 Combine 最佳实践,建议在 Xcode 14+ 环境中使用
