关于MVC的那些事

关于MVC的那些事_第1张图片
MVC

前言

很早前就想聊聊 MVC、MVVM,因为这是个非常有意思的话题,而且近些年来新的架构设计模式也层出不穷,除却 MVVM,还有 VIPER、ELM 等,如果不深入探究一下,很容易给人一种烟花渐欲迷人眼的错觉,没事,正如鲁迅曰过的,"一切的恐惧都源自于无知",(鲁迅:我说的?黑人❓),所以,掌握了整个事物的本质,也就多拥有了几分美好与泰然。
直觉告诉我,这应该会是个系列文章,今天就先着重说说 MVC 的那些事。

关于 MVC 的那些事

我们首先说说 MVC,MVC 这个概念最早是由 Tryge Reenskaug (施乐帕克实验室)于1979 年提出来的,原本是用它来描述 Small Talk 中的 "Template Pattern" 应用的,而 Cocoa 中的 MVC 的实现可以追溯到 1997 年左右的 NeXTSTEP 4 的时代。

在 iOS 开发中,不管是什么架构模式,基本上都是基于 MVC 演进而来。MVC 是 iOS 开发中使用最普遍的架构模式,同时也是苹果官方推荐的架构模式。

有着正统的历史,有着苹果的背书,那到底是哪里出了问题,导致 MVC 被人诟病不断从而不断提出新的架构设计方案呢?带着问题,我们来一步步深入了解下 MVC,答案自然会揭晓。

苹果所设想的 MVC 架构

我们先来看一下苹果所期望的 MVC 架构设想图:

关于MVC的那些事_第2张图片
MVC架构图

基于上图,我简要说明一下几点:

  • Controller 在编译期对 View 和 Model 都有强引用,知道自己所连接的的 View 和 Model 的接口,而 View 和 Model 对 Controller 是弱引用,即是在运行时才知道自己的监听者是谁。换句话说,即 View 和 Model 不依赖于 Controller,Model 和 View 之间也不相互依赖,提高了可复用性。
  • Controller 做的事情,就是调度 Model 与 View 之间的关系。说的简单些,就是把 Model 提供的数据丢给 View 去展示,监听 View 产生的用户事件去改变 Model 数据。

一个完整的流程大致如下,我们从一个用户事件开始:
用户点击 View 产生事件,View 发送 action 给 Controller,Controller 接收到事件后更改 Model,而 Model 的数据被更改后会通知监听者自己的变化(这时 Model 的监听者就是 Controller),Controller 检测到 Model 变化后,就使用最新的数据刷新 View 。至此,完成了一个完整的事件流传递过程。

我们从这个过程中看到了,数据的传递是以单向数据流流转的,而且 M、V、C 各自职责分明,C 负责调配 View 与 Model,Model 负责数据封装,View 负责展示数据,看似完美的架构方式,会有什么问题吗?

开发者实际使用的 MVC

鲁迅常说,"理想很丰满,现实很骨感"(鲁迅:又是我说的❓❓),下面就说一下,苹果的这种 MVC 设想方式,在落地实践的时候有哪些问题。

问题一:View?Controller?ViewController?傻傻分不清楚

这个问题的由来,源自于苹果本身的 Cocoa 设计。按照 Cocoa 的设计,总给人一种此 ViewController 非彼 Controller 的感觉,为什么这么说呢?
其一, ViewController 但从名字来看,就是好像是被设计为 View 的 Controller 而非 MVC 中的 C;
其二,ViewController 连 View 的整个生命周期都托管了,viewDidLoadviewWillAppearviewDidAppearviewXXX
种种迹象表明,按照苹果 Cocoa 的设计,View 跟 Controller 有一种扯不断理还乱的关系。仿佛变成了下面这幅图:

关于MVC的那些事_第3张图片
image.png
问题二:Controller 如果使用不当,很容易变成胖 Controller。

有人调侃,苹果的 MVC 其实就是 Massive ViewController。为什么容易变胖呢?好好活着不行吗?原因主要如下:
Controller 除了承担他本应承担的调配 View 跟 Model 的相互协调的工作外,还要加工组装数据,还承担了 View 相关的工作,再到后面繁重的业务逻辑一来的时候,都一股脑全塞给了 Controller。至此,Controller 彻底沦陷。Game Over。
这个问题大家应该可以感同身受,所以就不必多解释了。

问题三:过于强调 Model 与 View 的隔离,会让 View 层的封装性被破坏。

这一个问题可能大家还没反应过来是什么意思,这样,我先贴出一段代码,大家应该很快就能 Get 到我要表达的意思。

// UITableViewCell.h
@interface MCPostListCell : UITableViewCell
@property (nonatomic, strong) UIImageView *avatarView;
@property (nonatomic, strong) UILabel *nickLabel;
@property (nonatomic, strong) UILabel *ageLabel;
@property (nonatomic, strong) UILabel *groupLabel;
@property (nonatomic, strong) UILabel *locationLabel;
@property (nonatomic, strong) UILabel *signLabel;
...
...
...
@end

// Controller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MCPostListCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
    MCPostFloorModel *model = _dataSource[indexPath.item];
    [cell.avatarView sd_setImageWithURL:model.xxx];
    cell.nickLabel.text = model.xxx;
    cell.ageLabel.text = model.xxx;
    cell.groupLabel.text = model.xxx;
    cell.locationLabel.text = model.xxx;
    cell.signLabel.text = model.xxx;
    ...
    ...
    ...
    return cell;
}

这段代码,从 Cocoa 的设计规范上讲,没有什么可苛责的。毕竟 View 和 Model 不能产生关系,所以 View 只能通过暴露自己的接口给 Controller,让 Controller 从 Model 取值后去给 View 赋值。

但是直觉告诉我们,一个 TableViewCell 暴露出了 30 多个属性给 Controller 肯定是哪里出了问题,不太符合面向对象的设计规范,暴露出太多细节,这个 View 的会变得非常脆弱,容易被外部搞得一团糟,而且如果外部想渲染出自己想要的 View,还需要去关心 View 内部的实现细节,这显然不是我们想要的结果。

关于MVC的那些事_第4张图片
TeaTime
Apple 设计的 MVC 真的是那么不堪吗?

看到前面说到的 MVC 存在的几个问题,肯定会很疑惑,真的有那么不堪吗?
当然不是。
上面的问题,有的是源自于开发者对苹果 Cocoa 设计的不够深入或者理解偏差导致的,有的是我们可以对 MVC 稍微做下变体就能解决。
下面我们针对上面的问题一一给出回答。

解答问题一:提问:"View?Controller?ViewController?傻傻分不清楚"

其实,这个问题,我们误解了苹果。我先说出我的想法,ViewController 和 View 都同属于 C 的范畴。注意我这里说的 View 是控制器的 View。为了不引起歧义,后面统一把控制器的 View 称为 ContainerView。看下下面的图,应该就能深有体会。


关于MVC的那些事_第5张图片
MVC
解答问题二:提问:"Controller 如果使用不当,很容易变成胖 Controller"

关于这个问题,在给出方案之前,我先抛个反问:“你认真读过《代码整洁之道》吗?”
诚然,MVC 架构中的 C 容易变胖,不光 iOS,Android 中的 Activity 也会不经意间慢慢变胖。但是扪心自问一下,往 ViewController 里面塞代码的时候,有认真思考过这些代码是不是应该放在这里吗?

答案恐怕是,没认真思考过。"一段代码放 M 层不合适,V 层更不合适,那就只能放 C 层了。"这应该是大部分遇到胖 C 问题同学的心声吧。

具体怎么瘦身,其实核心思想就是 DRY 和单一职责原则。我们先看 ViewController 应该做的事,总之就是调度 M 和 V 层,管理 ViewContainer,具体如下:

  • 管理 View Container 的生命周期
  • 负责生成所有的 View 实例,并放入 View Container
  • 监听来自 View 与业务有关的事件,通过与 Model 的合作,来完成对应事件的业务。

明白了应该要做的事,再遇到一些其他的代码,放在 ViewController 之前是不是就会有意识地去思考一下了呢?Lighter View Controllers这篇文章已经总结的很好了,所以大家可以参照一下,为自己的 ViewController 瘦瘦身。

解答问题三:提问:"过于强调 Model 与 View 的隔离,会让 View 层的封装性被破坏"

其实不必完全按照 Cocoa 的设计样板来看待这件事情。其实按照 MVC 的原始概念,View 就是附着于 Model 上的一个产物,作用就是将 Model 内容展示出来。微软的 ASP .NET MVC 对 MVC 的理解就是基于这个概念。

关于MVC的那些事_第6张图片
.NET MVC

所以不必拘泥于必须 V、M 完全分离,MVVM 中的 V 也是对 VM 有一个强持有的。

你可能感兴趣的:(关于MVC的那些事)