使用 Vertical Slice Architecture 组织代码
了解如何使用垂直切片架构(Vertical Slice Architecture)来组织 SwiftUI 应用代码,提高代码复用性并更好地适应 iOS 17 新框架。

随着 SwiftUI 的普及,MVVM (Model-View-ViewModel) 毫无疑问是最常用的架构模式。然而,随着 iOS 17 引入 Observation 框架和 SwiftData,MVVM 架构模式面临新的挑战。
我们需要重新思考代码组织方式,以更好地适应这些变化,并提高代码的复用性。
什么是垂直切片架构 (VSA)?
垂直切片架构是一种按功能组织代码的方式,而非按技术层次。
每个功能模块(或"切片")包含实现该功能所需的所有组件,从用户界面到业务逻辑再到数据访问。
传统分层 vs 垂直切片
传统架构(如 MVC、MVVM)是"水平分层"的,通常按技术角色划分:
UI 层
↓
业务逻辑层
↓
数据访问层

而 VSA 则是垂直切分的:
功能1 功能2 功能3
UI UI UI
业务逻辑 业务逻辑 业务逻辑
数据 数据 数据

为什么现在考虑 VSA?
iOS 17 带来的变化
- SwiftData 与传统 MVVM 的不一致:
- SwiftData 鼓励在 View 中直接进行 CRUD 操作
- 例如:
modelContext.insert(item)
和modelContext.delete(item)
- 这与 MVVM 中"View 不应直接操作数据"的原则相悖
- Observation 框架简化了状态管理:
@Observable
宏减少了对传统 ViewModel 样板代码的需求- 状态更新变得更加直接和简洁
代码复用的困难
在实际开发中,我需要在不同项目间复用功能。
传统 MVVM 架构下:
- ViewModel 通常与特定应用的业务逻辑紧密耦合
- 不同项目间移植功能时,往往需要大量修改
- 依赖关系复杂,难以提取独立功能
VSA 在 SwiftUI 中的实现
VSA 项目文件结构
VSA 的典型文件结构如下:
Features/
Login/ // 登录功能切片
LoginView.swift // 视图
LoginViewModel.swift // 视图逻辑(如需要)
LoginModels.swift // 数据模型
Bookmark/ // 书签功能切片
BookmarkView.swift
BookmarkModel.swift // SwiftData 模型
BookmarkManager.swift // 业务逻辑
// 其他功能切片...
Shared/ // 共享组件
Styles/
Components/
Utils/

View-Model-Manager 模式
View-Model-Manager 的优势
由于 iOS17 带来的新特征,传统 MVVM 的一些假设被打破:
- 使用 @Observable 后,无需 ObservableObject 和 @Published
- SwiftData 鼓励直接在 View 中使用 modelContext。
使用 View-Model-Manager ,具有更加适合最新 SwiftUI 特征的职责划分:
- Model:负责数据结构。和 MVVM 一样。
- View:除了负责界面之外,还会进行简单的数据操作。使用 modelContext 直接更新 SwiftData 数据。
- Manager:负责复杂业务逻辑和状态管理。使用 Observation 宏,并在 App 中实例化后使用 Environment 依赖注入。
因此,我认为在垂直切片内部使用 View、Model 和 Manager 的组织方式可能比传统 MVVM 更合理。
VSA + MVM
让我们看一个简单的书签功能实现,展示 VSA 的组织方式:
// Features/Bookmark/BookmarkModel.swift
import SwiftData
@Model
final class Bookmark {
var id: UUID
var title: String
var url: String
var createdAt: Date
init(title: String, url: String) {
self.id = UUID()
self.title = title
self.url = url
self.createdAt = Date()
}
}
// Features/Bookmark/BookmarkManager.swift
import Foundation
import Observation
@Observable
final class BookmarkManager {
var isLoading = false
var errorMessage: String?
func fetchBookmarks(from url: URL) async {
isLoading = true
defer { isLoading = false }
do {
// 获取书签的逻辑
} catch {
errorMessage = "获取书签失败: \(error.localizedDescription)"
}
}
func validateURL(_ urlString: String) -> Bool {
// URL 验证逻辑
guard let url = URL(string: urlString),
UIApplication.shared.canOpenURL(url) else {
return false
}
return true
}
}
// Features/Bookmark/BookmarkListView.swift
import SwiftUI
import SwiftData
struct BookmarkListView: View {
@Environment(\.modelContext) private var modelContext
@Query private var bookmarks: [Bookmark]
@State private var manager = BookmarkManager()
var body: some View {
List {
ForEach(bookmarks) { bookmark in
BookmarkRow(bookmark: bookmark)
}
.onDelete(perform: deleteBookmarks)
}
.overlay {
if bookmarks.isEmpty {
ContentUnavailableView("暂无书签",
systemImage: "bookmark.slash")
}
}
}
private func deleteBookmarks(at offsets: IndexSet) {
for index in offsets {
modelContext.delete(bookmarks[index])
}
}
}
这种组织方式的关键是,整个功能所需的所有组件(模型、管理器、视图)都在同一个功能文件夹内,形成一个完整的"切片"。