12-30.【SwiftUI】如何避免 ViewModel/Presenter 反向依赖 UI 层?SwiftUI 中更难还是更容易?

8 阅读3分钟

在架构设计中,ViewModel 或 Presenter 的职责是**“处理业务逻辑并维护状态” ,而 UI 层的职责是“将状态绘制出来并捕捉用户动作”**。

“反向依赖”是指逻辑层(ViewModel)直接调用了 UI 层的具体实现(如 ViewControllerView 实例),或者依赖了 UI 层的特有类型(如 UIColorUIFont)。


1. 如何避免反向依赖:核心技术手段

A. 依赖倒置:协议(Protocol)是唯一的语言

ViewModel 不应该知道具体的 UI 类,它只应该通过抽象接口进行对话。

  • 错误viewModel.view = MyViewController()
  • 正确viewModel.delegate = MyViewProtocol

B. 消除 UI 类型污染

如果你的 ViewModel 里出现了 import UIKitimport SwiftUI,这就是警报。

  • 防御操作:使用 枚举 (Enum)结构体 (Struct) 替代。
  • 例子:不要让 ViewModel 返回 UIColor.red,让它返回 .error 状态,由 View 映射为红色。

C. 路由(Router)与协调器(Coordinator)

将“跳转”逻辑从 ViewModel 中抽离。ViewModel 只需要告诉协调器:“我已经完成了登录,请处理下一步”,而不关心是 push 一个 VC 还是 present 一个视图。


2. SwiftUI 中是更容易还是更难?

这是一个硬币的两面。结论是:设计上更容易,但“守规矩”更难。

为什么在 SwiftUI 中“更容易”?

  1. 数据驱动(Data-Driven)的本质: SwiftUI 本身就是单向的:State -> View。在 UIKit 中你可能需要手动调用 label.text = name(这是命令式的依赖),但在 SwiftUI 中,你只需更新 name 属性。这种声明式语法天生就切断了 ViewModel 对 View 的直接操控。
  2. 值类型(Value Types)的保护: SwiftUI 的 View 是结构体(Struct)。你无法在 ViewModel 中持久地持有 View 的引用,因为视图树是不断销毁和重建的。这在物理层面上阻止了反向持有。

为什么在 SwiftUI 中“更难”?

  1. 环境对象的诱惑@EnvironmentObject 极其方便,但也极其危险。开发者很容易在 ViewModel 中滥用全局状态,导致逻辑层与特定视图树的生命周期紧紧绑定。
  2. Navigation 的耦合: 在 SwiftUI 初版中,导航是通过 NavigationLink 绑定 isActive 状态实现的。这意味着 ViewModel 的状态必须直接控制 UI 的层级跳转。虽然现在有了 NavigationStackpath 绑定,但逻辑层和路由层的界限依然容易模糊。
  3. 闭包的循环引用: 在编写 Action 回调时,如果 ViewModel 捕获了 View 的某些闭包,而这些闭包又引用了 ViewModel,极易产生循环引用。

3. 防御式代码:SwiftUI 解耦模版

为了保证 ViewModel 绝对不反向依赖 UI,可以使用 Input/Output 模型

Swift

// 1. 彻底隔离 UI 库
import Foundation // 禁止 import SwiftUI

class UserViewModel: ObservableObject {
    // Output: 状态描述,View 只能读
    @Published private(set) var status: Status = .idle
    
    enum Status {
        case idle, loading, success(String), error
    }
    
    // Input: 意图(Action),View 通过这个函数告诉 VM 发生了什么
    func handleAction(_ action: Action) {
        switch action {
        case .loginTapped:
            performLogin()
        }
    }
    
    enum Action {
        case loginTapped
    }
}

总结

在 SwiftUI 中,**“数据即视图”**的思想让反向依赖在物理上变难了,但在逻辑上,由于 SwiftUI 强大的集成能力,保持 ViewModel 的“纯净”需要更强的架构自律。