SwiftUI VIPER Architecture
是一种将 VIPER 架构模式(View-Interactor-Presenter-Entity-Router)应用于 SwiftUI 的开发方式。VIPER 是一种模块化的架构设计模式,其目的是将项目逻辑分离,不同责任模块各司其职,从而提升代码的可维护性、可扩展性和测试能力。
这种架构与 SwiftUI 的组合可能显得不太直观,因为 SwiftUI 的设计哲学更偏向于简单和声明式的编程模式,而 VIPER 却是一种更为正式且复杂的架构。结合 SwiftUI 与 VIPER 能够帮助开发者更清晰地划分职责模块,同时仍能保持 SwiftUI 的高效开发特性。
以下是对 SwiftUI 中 VIPER 架构 的详细讲解。
VIPER 是什么? #
VIPER 是一种架构模式,名字由以下 5 个核心部分构成:
View(视图):
- 用户界面,用于展示 UI,接收用户输入并将事件传递到
Presenter
。 - 在 SwiftUI 中,
View
是一个 SwiftUI 的声明式视图结构,如ContentView
。
- 用户界面,用于展示 UI,接收用户输入并将事件传递到
Interactor(交互器):
- 负责业务逻辑和数据处理,处理来自
Presenter
的请求。 - 从数据层(例如网络请求或本地数据库)获取数据,并将数据返回给
Presenter
。
- 负责业务逻辑和数据处理,处理来自
Presenter(展示逻辑):
- 中间层,负责将
Interactor
的处理结果转换为适合View
使用的数据。 - 同时处理用户交互事件,并将其转发到
Interactor
,然后将反馈返回给View
。
- 中间层,负责将
Entity(实体):
- 应用程序中的核心模型(数据结构),通常是数据存储或数据定义类型。
- 它是纯粹的模型类,通常不直接与视图交互。
Router(路由器):
- 负责模块之间的导航逻辑。
- 在 SwiftUI 中,
Router
通常处理视图之间的导航,例如NavigationLink
、Sheet
或FullScreenCover
。
为什么将 VIPER 用于 SwiftUI? #
SwiftUI 的核心思想是使用 State
和 Binding
在 UI 和数据之间建立声明式绑定关系。从理论上讲,较小的应用可以非常简单地通过 MVVM
(Model-View-ViewModel)来实现。然而,当应用变得复杂时,SwiftUI 项目可能会出现逻辑与用户界面混杂在一起的问题。VIPER 的引入可以解决以下问题:
清晰的模块分离:
- 将视图、业务逻辑、数据管理及路由逻辑完全独立,避免非职责代码混杂。
便于测试:
- VIPER 的独特分层设计使单元测试更容易(例如单独测试
Interactor
或Presenter
)。
- VIPER 的独特分层设计使单元测试更容易(例如单独测试
扩展性强:
- 随着组件模块的增多,VIPER 架构可以帮助在大型项目中管理逻辑,尤其是多模块场景。
更好的代码复用性:
- 业务逻辑清晰地被抽象并分离,跨模块复用组件变得更加容易。
SwiftUI 上的 VIPER 结构 #
以下是 SwiftUI 中结合 VIPER 的模块设计流程。
1. View: 视图 #
视图负责定义用户界面,并接收用户的输入。它会调用 Presenter
来获取 UI 所需的数据,同时将用户的事件转发给 Presenter
。
示例代码: #
struct TodoListView: View {
@ObservedObject var presenter: TodoListPresenter
var body: some View {
VStack {
List(presenter.todos) { todo in
Text(todo.title)
}
Button("Add Todo") {
presenter.addNewTodo()
}
}
}
}
- 使用
@ObservedObject
绑定Presenter
,让视图能够根据数据变化自动更新。
2. Interactor: 业务逻辑 #
Interactor
负责处理应用的核心业务逻辑,例如与远程 API 交互或从数据库读取本地数据。
示例代码: #
class TodoListInteractor {
private var todoRepository: TodoRepository
init(todoRepository: TodoRepository) {
self.todoRepository = todoRepository
}
func fetchTodos() -> [Todo] {
return todoRepository.getTodos()
}
func createTodo(title: String) {
todoRepository.addTodo(Todo(title: title))
}
}
- 这里的
TodoRepository
是数据源。 Interactor
是一种纯粹的业务逻辑实现,它把输入和输出传递给Presenter
。
3. Presenter: 数据适配器 #
Presenter
是视图和交互器之间的中介。它负责获取来自 Interactor
的数据,并将这些数据转化为适合 View
显示的格式。
示例代码: #
class TodoListPresenter: ObservableObject {
@Published var todos: [Todo] = []
private var interactor: TodoListInteractor
init(interactor: TodoListInteractor) {
self.interactor = interactor
}
func loadTodos() {
self.todos = interactor.fetchTodos()
}
func addNewTodo() {
interactor.createTodo(title: "New Task")
loadTodos()
}
}
Presenter
将Interactor
的数据公开为通过@Published
修饰的可观察数据,通知View
更新。
4. Entity: 数据模型 #
实体是核心数据结构,无需关于应用操作逻辑的任何关系。
示例代码: #
struct Todo: Identifiable {
let id = UUID()
let title: String
}
Entity
是简单的struct
或class
,仅代表应用的数据。
5. Router: 路由导航 #
Router
负责页面之间的导航和跳转逻辑。例如,用户点击按钮后跳转到详情页,Router
会负责将这个操作连接到相应的模块。
示例代码: #
class TodoListRouter {
func navigateToDetailView(todo: Todo) -> some View {
return TodoDetailView(todo: todo)
}
}
Router
将负责具体的视图切换,如在 SwiftUI 中使用NavigationLink
或Sheet
。
完整示例架构整合 #
假设一个简单的待办事项列表应用,展示整个 SwiftUI VIPER 模块的关系。
TodoListView
(View)负责展示任务列表,并绑定TodoListPresenter
。TodoListPresenter
(Presenter)控制视图状态变化。TodoListInteractor
(Interactor)执行数据库读取/写入操作。Todo
(Entity)是任务的数据结构。TodoListRouter
(Router)处理页面切换,例如点开任务详情页。
VIPER 优势与挑战 #
优势: #
- 代码组织清晰:
- 各个模块职能划分明确,便于维护和扩展。
- 高可测试性:
- VIPER 的各层都可以独立测试,例如测试
Interactor
的业务逻辑或Presenter
的数据绑定。
- VIPER 的各层都可以独立测试,例如测试
- 复用组件:
- 模块高度解耦,
Interactor
和Presenter
等组件很容易在多个页面中复用。
- 模块高度解耦,
- 复杂应用适配:
- 非常适合大型项目或包含复杂业务逻辑的应用。
挑战: #
- 开发成本高:
- VIPER 的模块划分会带来比其他架构更多的样板代码,对于小型应用可能显得过于复杂。
- 学习门槛:
- 对初学者来说,SwiftUI 的声明式开发显得简单易用,而引入 VIPER 增加了复杂度。
- SwiftUI 的显式约束:
- SwiftUI 的声明性哲学与 VIPER 的分层思想可能会产生冲突。
总结 #
在 SwiftUI 中应用 VIPER 是一种模块化、职责清晰的架构选择,非常适合大型、复杂的项目开发,但对于小型或简单应用可能显得过于笨重。
如果你:
- 项目复杂 —— 使用 VIPER 是更优选择。
- 项目简单 —— 考虑更轻量的 MVVM 架构。