View 的最大的任务就是向 Controller 传递用户动作事件。
ViewController 不再承担一切代理和数据源的职责,通常只负责一些分发和取消网络请求以及一些其他的任务。
- Cocoa MVC
- * 由于 Controller 是一个介于 View 和 Model 之间的协调器,所以 View 和 Model 之间没有任何直接的联系。Controller 是一个最小可重用单元,这对我们来说是一个好消息,因为我们总要找一个地方来写逻辑复杂度较高的代码,而这些代码又不适合放在 Model 中。
- 理论上来讲,这种模式看起来非常直观,但你有没有感到哪里有一丝诡异?你甚至听说过,有人将 MVC 的缩写展开成 (Massive View Controller),更有甚者,为 View controller 减负也成为 iOS 开发者面临的一个重要话题。如果苹果继承并且对 MVC 模式有一些进展,所有这些为什么还会发生?
Realistic Cocoa MVC
* Cocoa 的 MVC 模式驱使人们写出臃肿的视图控制器,因为它们经常被混杂到 View 的生命周期中,因此很难说 View 和 ViewController 是分离的。尽管仍可以将业务逻辑和数据转换到 Model,但是大多数情况下当需要为 View 减负的时候我们却无能为力了,View 的最大的任务就是向 Controller 传递用户动作事件。ViewController 不再承担一切代理和数据源的职责,通常只负责一些分发和取消网络请求以及一些其他的任务。
- 你可能会看见过很多次这样的代码:
BookModel *bookModel = [myDataArray objectAtIndex:indexPath.row];
[cell configWithModel:bookModel];
- * 这个 cell,正是由 View 直接来调用 Model,所以事实上 MVC 的原则已经违背了,但是这种情况是一直发生的甚至于人们不觉得这里有哪些不对。如果严格遵守 MVC 的话,你会把对 cell 的设置放在 Controller 中,不向 View 传递一个 Model 对象,这样就会大大减少 Controller 的体积。Cocoa 的 MVC 被写成 Massive View Controller 是不无道理的。
- 直到进行单元测试的时候才会发现问题越来越明显。因为你的 ViewController 和 View 是紧密耦合的,对它们进行测试就显得很艰难--你得有足够的创造性来模拟 View 和它们的生命周期,在以这样的方式来写 View Controller 的同时,业务逻辑的代码也逐渐被分散到 View 的布局代码中去。
MVC 是一个用来组织代码的权威范式,也是构建 iOS App 的标准模式。Apple 甚至是这么说的。在 MVC 下,所有的对象被归类为一个 model,一个 view,或一个 controller。Model 持有数据,View 显示与用户交互的界面,而 View Controller 调解 Model 和 View 之间的交互。然而,随着模块的迭代我们越来越发现 MVC 自身存在着很多不足。
1)MVC 在现实应用中的不足:
* 在 MVC 模式中 view 将用户交互通知给控制器。view 的控制器通过更新 Model 来反应状态的改变。Model(通常使用 Key-Value-Observation)通知控制器来更新他们负责的 view。大多数 iOS 应用程序的代码使用这种方式来组织。
2)愈发笨重的 Controller:
* 在传统的 app 中模型数据一般都很简单,不涉及到复杂的业务数据逻辑处理,客户端开发受限于它自身运行的的平台终端,这一点注定使移动端不像 PC 前端那样能够处理大量的复杂的业务场景。然而随着移动平台的各种深入,我们不得不考虑这个问题。传统的 Model 数据大多来源于网络数据,拿到网络数据后客户端要做的事情就是将数据直接按照顺序画在界面上。随着业务的越来越来的深入,我们依赖的 service 服务可能在大多时间无法第一时间满足客户端需要的数据需求,移动端愈发的要自行处理一部分逻辑计算操作。这个时间一惯的做法是在控制器中处理,最终导致了控制器成了垃圾箱,越来越不可维护。
- 控制器 Controller 是 app 的 “胶水代码”,协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的 loading、appearing、disappearing 等等,同时往往也会充满我们不愿暴露的 Model 的模型逻辑以及不愿暴露给视图的业务逻辑。这引出了第一个关于 MVC 的问题…
- 视图 view 通常是 UIKit 控件(component,这里根据习惯译为控件)或者编码定义的 UIKit 控件的集合。进入 .xib 或者 Storyboard 会发现一个 app、Button、Label 都是由这些可视化的和可交互的控件组成。View 不应该直接引用 Model,并且仅仅通过 IBAction 事件引用 controller。业务逻辑很明显不归入 view,视图本身没有任何业务。
- 厚重的 View Controller 由于大量的代码被放进 viewcontroller,导致他们变的相当臃肿。在 iOS 中有的 view controller 里绵延成千上万行代码的事并不是前所未见的。这些超重 app 的突出情况包括:厚重的 View Controller 很难维护(由于其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),导致协议的响应代码和 controller 的逻辑代码混淆在一起。
- 厚重的 view controller 很难测试,不管是手动测试或是使用单元测试,因为有太多可能的状态。将代码分解成更小的多个模块通常是件好事。
3)太过于轻量级的 Model:
* 早期的 Model 层,其实就是如果数据有几个属性,就定义几个属性,ARC 普及以后我们在 Model 层的实现文件中基本上看不到代码(无需再手动管理释放变量,Model 既没有复杂的业务处理,也没有对象的构造,基本上 .m 文件中的代码普遍是空的);同时与控制器的代码越来厚重形成强烈的反差,这一度让人不禁对现有的开发设计构思有所怀疑。
4)遗失的网络逻辑:
* 苹果使用的 MVC 的定义是这么说的:所有的对象都可以被归类为一个 Model,一个 view,或是一个控制器。就这些,那么把网络代码放哪里?和一个 API 通信的代码应该放在哪儿?
- 你可能试着把它放在 Model 对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的 Model 生命周期更长,事情将变的复杂。显然也不应该把网络代码放在 view 里,因此只剩下控制器了。这同样是个坏主意,因为这加剧了厚重控制器的问题。那么应该放在那里呢?显然 MVC 的 3 大组件根本没有适合放这些代码的地方。
5)较差的可测试性
* MVC 的另一个大问题是,它不鼓励开发人员编写单元测试。由于控制器混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不做任何测试。
- 上文提到了控制器可以管理视图的层次结构;控制器有一个 “view” 属性,并且可以通过 IBOutlet 访问视图的任何子视图。当有很多 outlet 时这样做不易于扩展,在某种意义上,最好不要使用子视图控制器(child view controller)来帮助管理子视图。在这里有多个模糊的标准,似乎没有人能完全达成一致。貌似无论如何,view 和对应的 controller 都紧紧的耦合在一起,总之,还是会把它们当成一个组件来对待。Apple 提供的这个组件一度以来在某种程度误导了大多初学者,初学者将所有的视图全部拖到 xib 中,连接大量的 IBoutLet 输出口属性,都是一些列问题。