iOS中MVVM架构正确实现的相关技术疑问
1. 简单VC+VM场景下,是否仍需遵循SOLID的“依赖抽象”原则?
说实话,哪怕是最基础的VC和VM组合,遵循**依赖倒置原则(D)**依然是有价值的,只是得权衡样板代码的成本。
举个实际的例子:如果你的VM直接在viewDidLoad里实例化,比如let vm = MyViewModel(),那后续要给这个VC写单元测试的时候,你根本没法Mock VM的逻辑——总不能每次测试都跑真实的业务代码吧?但如果定义个MyViewModelProtocol,让VM实现它,VC依赖这个协议而非具体类,那测试时就能轻松替换成Mock实现,验证VC的UI逻辑是否正确。
至于样板代码的问题,其实可以用Swift的特性来简化:比如给协议加默认实现,或者用扩展把通用逻辑抽出来,不用每次都写一堆重复的方法声明。如果你的项目后续有扩展的可能(比如以后要给这个VC换个VM实现,或者加新的业务逻辑),那这点前期的“麻烦”绝对值得;但如果这个VC真的是一次性的、永远不会变的简单页面(比如一个静态的关于页),那直接用具体类也不是不可接受——不过这种情况真的很少见,业务需求总是会变的。
总结下来:优先遵循依赖抽象,除非你能100%确定这个页面永远不会迭代、不需要测试。
2. 根ViewController的ViewModel:属性注入还是直接实例化?
这个得看你的根VM有没有外部依赖:
- 如果你的根VM是个“纯逻辑”的类,不需要依赖网络、存储这些外部服务,那直接在
viewDidLoad里实例化完全没问题——毕竟根VC是App的入口,它的依赖关系通常很简单,不会有太多替换的需求。 - 但如果根VM需要依赖其他组件(比如
APIService、UserStorage),那通过AppDelegate做属性注入会更合理。这样你可以在App启动时统一管理这些依赖,后续测试根VC的时候,也能替换成Mock的服务实现。
另外,如果你以后想引入依赖注入容器(比如Swinject这类),提前用属性注入的方式会让过渡更顺畅。当然,要是你的项目很小,完全没有测试需求,直接实例化也不会有什么大问题——开发效率有时候也很重要。
3. 委托vs Boxing方案:哪个更适合数据绑定?
这俩我都用过,得看具体场景:
委托的优势
- 逻辑非常直观:每个回调都有明确的方法名,团队协作时别人一看就懂哪个方法对应什么交互。
- 适合多回调、复杂交互的场景:比如VC需要通知VM用户做了多个不同的操作(下拉刷新、点击按钮、输入文本),用代理方法分组会很清晰。
Boxing方案的优势
- 代码更简洁:不用写代理协议、不用设置
delegate,直接用闭包或者Observable的Boxing实现,比如:class MyViewModel { let title = Box<String>("初始标题") } // 在VC里监听 vm.title.bind { [weak self] newTitle in self?.titleLabel.text = newTitle } - 更适合单向数据绑定:比如VM只需要通知VC更新某个UI元素,Boxing的写法比写一个代理方法轻便太多,减少了样板代码。
哪个更优?
如果只是简单的“VM通知VC更新UI”,Boxing是更优的选择,代码更清爽,也符合轻量响应式的思路;但如果是VC和VM之间有大量双向交互,或者需要明确区分不同的回调类型,那委托的可读性会更好。
另外,Boxing方案要注意避免循环引用,记得用[weak self]或者unowned,这点和闭包的坑一样。
内容的提问来源于stack exchange,提问作者wm.p1us

