关于MVC的那些事

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架构图

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

  • 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 有一种扯不断理还乱的关系。仿佛变成了下面这幅图:

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 内部的实现细节,这显然不是我们想要的结果。

TeaTime
Apple 设计的 MVC 真的是那么不堪吗?

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

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

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


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 的理解就是基于这个概念。

.NET MVC

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容