【iOS】MVVM

MVC模式 MVC的前世: 这个架构中,三个实体联系太过紧密,每个实体都知道另外的两个实体。这就导致了复用性能急剧下降。 apple MVC : 苹果官方推荐使用的MVC,结构大致如下:https://developer.apple.com/library/archive/doc

作者 TommyWu
封面圖片: 【iOS】MVVM

#MVC 模式

MVC 的前世:

Traditional MVC

这个架构中,三个实体联系太过紧密,每个实体都知道另外的两个实体。这就导致了复用性能急剧下降。

apple MVC :

苹果官方推荐使用的 MVC,结构大致如下:https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

Model-View-Controller design pattern

友链:https://www.jianshu.com/p/9e8fd85b61e0

可以看出ViewModel事实上是没有交互的,由Controller负责ModelView之间的交互,交互越多,Controller就越臃肿,更别提实际运用中有些还去掉了View层或者Model层。目前对 MVC 架构划分是Model作为数据管理者View作为数据展示者Controller作为数据加工者

然而在 iOS 中Controller中由于有苹果内定的一些视图的生命周期在里面,比如viewDidLoad等等,于是就出现了一些关于 iOS 的 MVC 架构方面的争论,有些认为在 iOS 开发中并没有什么ViewController,只有Model+ViewController

这里,给出一个某大咖关于 MVC 框架的理解:

Model 应该做的: - 给 ViewController 提供数据 - 给 ViewController 储存数据提供接口 - 提供经过抽象的业务逐渐,供 Controller 调度 Controller 应该做: - 管理生命周期 - 生成所有的 View 的实例,并放入 ViewController - 监听来自 View 与业务有关的时间,通过和 Model 合作,来完成对应事件的业务。 View 应该做: - 响应与业务无关的时间,并因此引发动画效果,点击反馈(如果合适还是放在 View 去做) - 界面元素表达

但是,几十年过去了,我们对于 MVC 这种设计模式真的用得好吗?其实不是的,MVC 这种分层方式虽然清楚,但是如果使用不当,很可能让大量代码都集中在 Controller 之中,让 MVC 模式变成了 Massive View Controller 模式。(巨屎控制器)

MVC 现实情况

Realistic Cocoa MVC

Cocoa MVC鼓励你使用大型的视图控制器(Massive View Controllers),由于他们都参与到了视图(View)的生命周期中了以至于很难说他们是分离的。尽管你仍有能力分流一些业务逻辑和数据转换功能到模型(Model)中,但是当涉及到把工作分流到视图(View)中去时你就诶有更多的选择了,因为在大多数时候视图(View)的所有职责是把动作传递到控制器(Controller)中。视图控制器(View Controller)最终最为所有控件的委托和数据源,通常负责调度和取消网络请求… 应有尽有

因此,M——VC 可能是对 iOS 开发中iOS模式更为准确的解读,也更为准确地描述了我们日常开发编写的 MVC 代码。

就像在日常开发中的制定也 cell,正是直接由 View 来调 Model,所以事实上典型的 MVC 以及违背了,但是人们一直不觉得这有哪些不对。如果严格遵守MVC的话,你会把对cell的设置放在Controller中,不向View传递一个Model对象,这样就会大大增加Controller的体积

综上所述,Cocoa MVC是一个相当糟糕的事情。但是如果你没有打算在项目架构上耗费太多时间,那么他就是你的最好选择。。

Cocoa MVC is the best architectural pattern in term of the speed of the development. 在开发速度上面Cocoa MVC是最好的架构模式。

img

#如何解决 Controller 臃肿的问题?

这里我摘录一段博客:

对于 View 来说,你如果抽象得好,那么一个 App 的动画效果可以很方便地移植到别的 App 上,而 Github 上也有很多 UI 控件,这些控件都是在 View 层做了很好的封装设计,使得它能够方便地开源给大家复用。 对于 Model 来说,它其实是用来存储业务的数据的,如果做得好,它也可以方便地复用。 说完 View 和 Model 了,那我们想想 Controller,Controller 有多少可以复用的?我们写完了一个 Controller 之后,可以很方便地复用它吗?结论是:非常难复用。在某些场景下,我们可能可以用 addSubViewController 之类的方式复用 Controller,但它的复用场景还是非常非常少的。 如果我们能够意识到 Controller 里面的代码不便于复用,我们就能知道什么代码应该写在 Controller 里面了,那就是那些不能复用的代码。在我看来,Controller 里面就只应该存放这些不能复用的代码,这些代码包括: - 在初始化时,构造相应的 View 和 Model。 - 监听 Model 层的事件,将 Model 层的数据传递到 View 层。 - 监听 View 层的事件,并且将 View 层的事件转发到 Model 层。 如果 Controller 只有以上的这些代码,那么它的逻辑将非常简单,而且也会非常短。

#把每一个网络请求封装为单例类

这其实是设计模式中的 Command 模式,优点如下:

  • ** 解耦网络库:**C / M 不直接依赖 AFN
  • ** 公共逻辑统一处理:** 鉴权、token、版本号、统一错误处理、统一 header 等都可以在基类处理
  • ** 缓存 / 数据持久化:** 可以自行实现(如离线队列等)
  • ** 更好的拓展性能:** 支持插件、日志、JSON 校验、请求组合(并行 / 串联)、短点续传等

这部分代码从 Controller 剥离后,不但简化了 Controller 逻辑,也达到了网络层的代码复用

Controller / ViewModel ↓ 使用 Request 对象(构造 + 配置) Request (BaseRequest) ↓ 调用 RequestManager(封装 AFNetworking) RequestManager -> AFHTTPSessionManager ↓ 插件/拦截器(可选) Response / Error / Cache

#把界面拼装到专门类中

新手喜欢在 controller 中把 UILabel UIButton UITextfield 往self.view上用addsubView方法放。可以两种方法把代码从 Controller 剥离。

  • 构建专门的 UIView 子类,来负责这些控件的拼装。不过稍微麻烦的是,你需要把这些控件的事件回调先接管,再一一暴露回 controller
  • 用一个静态 Util 类,帮你做 UIView 的拼接工作。

方法一比较适合大型的项目,需要统一样式与行为。而方案二适合于少量封装和小工程量代码

#构造 ViewModel

MVC 不是不能用 ViewModel? MVVM 的优点一样可以借鉴。具体来说就是吧 ViewController 给 View 传递数据这个过程,抽象为构建 ViewModel 的过程。

ViewMode 包含:

  • 从 service 获取 ViewModel
  • 把 ViewModel 交给 View

同时在具体的实践中,我们可以创建构造专门的 ViewModel 工厂类。

那么又要有人问了,这不是和 MVVM 一样了吗?其实不然。MVC + ViewModel只是多了一个数据的格式化层,而真正的 MVVM 使用ViewModel和双向绑定让 View 和 VM 自动同步。 核心区别有两点:

  • 有没有绑定
  • ViewModel 的职责是否拓展到事件处理和状态管理

真正的 MVVM 中,ViewModel 输出 UI 所需要的状态并处理用户输入,而Controller只是负责把 View 的事件绑定到 ViewModel 的 input,吧 View 的 UI 绑定到 ViewModel 的 output。而 MVC 中的 VM 只是把 Controller 的逻辑挪出去而已,

#专门构建储存类

storage 专门做:

  • 本地缓存
  • 数据迁移

把所有本地数据读写、缓存策略、迁移逻辑集中在一个 Storage(或 Repository)层,Controller 和 ViewModel 只通过接口访问,不了解底层实现(NSUserDefaults / sqlite / Realm / 文件等)。符合单一职责原则

All in all:

通过这些操作,Controller 中最终只有两类代码: 生命周期和事件响应。

Controller 本身不写:网络、数据处理、UI 布局、业务逻辑、存储逻辑、字符串拼接、格式化。

#MVVM 模式

MVVM,则是由 MVC 演化来的:

在这里插入图片描述

抽出ViewMode层负责数据与视图的交互部分,它主要作用是拿到原始的数据,根据具体业务逻辑需要进行处理,之后将处理好的东西塞到 View 中去,其职责之一是静态模型,表示 View 显示自身所需的数据,这使 View 具有更清晰定义的任务,即呈现视图模型提供的数据,总结为一句话就是与 View 直接对应的 Model,逻辑上是属于 Model 层

Controller仅协调各部分的绑定关系以及逻辑处理,唯一关注的是使用来自 ViewModel 的数据配置和管理各种 View,不需要了解 web 调用,model 对象等,这些都交给 ViewModel 操作。

img

img

ViewModel 并不一定只服务一个 View,同理,一个 Controller 也可以持有很多 ViewModel,来实现不同的逻辑控制多重的 View。

Controller 唯一关注的是使用来自 ViewModel 的数据配置和管理各种 View,并让 ViewModel 知道何时发生需要更改上游数据的相关用户输入。 Controller 不需要知道网络请求、数据库操作以及 Model 等,这样就让 Controller 更集中的去处理具体的业务逻辑。

img

模块层级图:

img

MVVM中,View 和 ViewCotroller 正式联系在一起,我们把他视为一个组件,他们都不能直接引用 Model,而是应用视图模型 ViewModel,虽然会轻微增加代码量,但是总体上减少了代码的复杂度。

MVVM 的注意事项

  • view 引用viewModel ,但反过来不行(即不要在viewModel中引入#import UIKit.h,任何视图本身的引用都不应该放在viewModel中)(PS:必须满足
  • viewModel 引用model,但反过来不行
  • ViewController 尽量不涉及业务逻辑,让ViewModel去做这些事情

大致实现以下效果:

图片一

图片二

MVC

优点: 通用架构; 处理耦合度高的逻辑方便; 缺点: 耦合度高; 复用性差; 测试性差;

MVVM

优点: 耦合度低; 复用性高; 测试性高; 层次更清晰; 重构成本低; 缺点: 处理耦合度高的逻辑比较复杂; 若加入 RAC,增加学习成本; 一些 Bug 比较难调试

MVVM 在使用当中,通常还会利用双向绑定技术,使得 Model 变化时,ViewModel 会自动更新,而 ViewModel 变化时,View 也会自动变化。所以,MVVM 模式有些时候又被称作:model-view-binder 模式。

#MVVM 的问题

在实际使用中,确实能够使得 Model 层和 View 层解耦,但是如果实现 MVVM 中的双向绑定的话,就需要引入更多复杂的框架实现了。

某种意义来说,就是数据绑定使 MVVM 变得复杂难用了。由于 Obj - C 没有像 Swift 那样的原生数据绑定支持,我们需要手动实现数据绑定机制。

  • KVO View 可以监听 ViewModel 中属性的变化,并自动更新。但是比较难用容易出问题,最好还是用 RAC 封装
  • 通知中心(NSNotifiction)提供了一种松耦合的通信方式,ViewModel 可以通过通知来告知 View 状态的变化。适用于有全局事件
  • 代理模式(Delegate)ViewModel 可以定义协议,让 View 遵循协议并实现相应的方法来响应状态变化。
  • block 最简单最小巧(黎果菲方案,组长评价 “很可以”)
  • RAC 第三方库

在这里插入图片描述

#block

block 的作用:保存一段代码,到恰当的时候调用, 很多时候 block 是代理的一种优化方案

  • block 比 protocol 更灵活,更高聚合,低耦合。 例如 AFN 的网络框架中,就可以将 “准备请求参数” 的代码和 “处理后台返回数据” 的代码放在一起。
  • block 的灵活还体现在他可以当作方法参数以及返回值。 Block 可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。

#KVO

实现的方式中 KVO 不需要通知中心将可以实现属性的监听;与 block 以及代理相比,可以减少大量的代理方法以及 block 中的处理逻辑代码

- (void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context

anObserver:观察者对象,这个对象必须实现observeValueForKeyPath:ofObject:change:context:方法,以响应属性的修改通知, 否则将报错An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

keyPath:被监听的属性。这个值不能为 nil。

options:监听选项,这个值可以是 NSKeyValueObservingOptions 选项的组合。关于监听选项,我们会在下面介绍。

context:任意的额外数据,我们可以将这些数据作为上下文数据,它会传递给观察者对象的 observeValueForKeyPath:ofObject:change:context

。这个参数的意义在于用于区分同一对象监听同一属性的多个不同的监听。

- (void)dealloc {
// 移除监听
[self.webVIew removeObserver:self forKeyPath:@"xxxx"];
}

KVO

#ReactiveCocoa

他可以监听 UI 事件、文本变化、属性变化(不需要写 KVO),代替代理 / 回调。

ReactiveCocoa(简称为RAC),是由Github开源的一个应用于 iOS 和 OS 开发的新框架。RAC结合了函数式编程(Functional Programming)响应式编程(React Programming)的框架,也可称其为函数响应式编程(FRP)框架 。 函数响应式编程利用下图

来解释最好不过了:c = a + b 定义好后,当a的值变化后,c的值就会自动变化。不过a的值变化时会产生一个信号,这个信号会通知c根据a变化的值来变化自己的值。b的值变化同样也影响c的值,这就是函数响应式编程。

img

RAC 是函数响应式编程(FRP)框架。ReactiveCocoa 结合了几种编程风格:函数式编程(Functional Programming)响应式编程(Reactive Programming)

使用 RAC 解决问题,就不需要考虑调用顺序。每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理

CleanShot 2025-12-09 at 19.35.15@2x

函数式编程(Functional Programming)和响应式编程(React Programming)也是当前很火的两个概念,它们的结合可以很方便地实现数据的绑定。于是,在 iOS 编程中,ReactiveCocoa 横空出世了,它的概念都非常 新,包括:

  • 函数式编程(Functional Programming),函数也变成一等公民了,可以拥有和对象同样的功能,例如当成参数传递,当作返回值等。看看 Swift 语言带来的众多函数式编程的特性,就你知道这多 Cool 了。
  • 响应式编程(React Programming),原来我们基于事件(Event)的处理方式都弱了,现在是基于输入(在 ReactiveCocoa 里叫 Signal)的处理方式。输入还可以通过函数式编程进行各种 Combine 或 Filter,尽显各种灵活的处理。
  • 无状态(Stateless),状态是函数的魔鬼,无状态使得函数能更好地测试。
  • 不可修改(Immutable),数据都是不可修改的,使得软件逻辑简单,也可以更好地测试。

关于这个第三方库的具体内容笔者现在还没有具体了解,具体的使用以及源码的相关内容会在后续更新。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


原文发布于 CSDN:【iOS】MVVM