Model-View-ViewModel for iOS

来源:互联网 发布:cda数据分析师怎么样 编辑:程序博客网 时间:2024/05/16 19:54

原文地址

自己写iOS代码也有几年了,但是经常会写出无比复杂的ViewController。虽然也曾经实践过一些缩减代码的方法,但终究没有一个完整的章法。直到看完这篇文章,内心都点小激动,所以决定翻译一下。


如果你从事iOS开发已经有一段时间的话,那你肯定听说过Model-View-Controller(MVC)。 这是一种构建iOS应用程序的标准方法。但是最近我对MVC模式的短板越来越无法忍受了。本文我将介绍什么是MVC,尤其是它的不足,并且告诉你一种新的APP架构设计方式:Model-View-ViewModel。扔掉你那时髦的宾果卡吧,因为我们要进行范式的切换了。

Model-View-Controller

Model-View-Controller是用来设计代码结构的一种范式。苹果公司甚至这样说 。在MVC模式下,所有的对象要么是Model,要么是View,否则就是Controller。Model持有数据,View向用户展示一个互动的界面,Controller则居间协调Model和View之间的通信。

MVC
MVC

从上图中可以看出,View通知Controller任何用户的交互操作。然后Controller根据状态的变化更新Model。接着Model(通常是借助Key-Value-Observation)再通知Controller来更新相应的View。这种居间协调的方式会产生很多的代码。

Model对象通常极其简单。很多时候,它们是Core Data托管对象,如果你回避Core Data,也可以是其他热门的对象层。根据苹果的说法,Model包含数据和对数据进行操作的逻辑。在实践中,Model层经常被实现的非常薄,不论好坏与否,Model的逻辑被写到Controller中去了。

View(通常)要么是UIKit组件,要么是写代码定义的UIKit组件的集合。他们是xib或者StoryBoard的一部分:APP上看得见摸得着的东子。具体是Button还是Label,由你来决定。View永远不应该和Model产生直接的引用,它只能和Controller通过IBAction事件产生联系。业务逻辑和View无关,View本身不包含业务逻辑。

就剩下Controller了。Controller就是胶水代码的发源地:用来连接Model和View交互的代码。Controller负责管理自身包含的View的层级结构,响应View的加载、出现、消失等事件。Controller还包含了模型逻辑(Model层没有实现)和业务逻辑(View层没有实现)。这就导致了MVC的第一个问题......

Massive View Controller

由于大量的代码被放置在ViewController,使它变得非常膨胀。我们也不是没听过iOS的ViewController中有成千上万行的代码。这些大块头代码是我们的程序变得不好了:复杂的ViewController难以维护(实在是太长了),属性一大推连状态都不好管理,实现了一大堆协议把协议响应代码和Controller逻辑混在一起。

复杂的ViewController难以测试,无论是手工测还是单元测试,因为可能的状态实在太多了。把代码分解成更小的模块是一件很美好的事情,不由想起了一个最近的故事。

Missing Network Logic

根据苹果对MVC的定义,任何对象只能是Model、View、Controller三者之一。那么哪里来放网络相关的代码呢?和后台API交互的代码应该放在哪里?

你可以耍小聪明把它放在Model层,但这样似乎有点牵强,因为网络请求是异步的,所以如果网络请求超过了所在Model的生命周期,又在怎么办?好吧,越来越复杂了。你当然也不能把网络请求放在View层,所以只剩下了......Controller。这也不是一个好主意,因为这样会造成Massive View Controller的问题。

那该放在哪里呢?MVC确实没有除了三大块之外放代码的地方。

Poor Testability

另外一个MVC的大问题就是它不鼓励开发者编写单元测试。因为ViewController混合了对View的操作和业务逻辑,把这些代码区分开以便写单元测试变得十分艰巨,所以我们干脆就不写单元测试了。

Fuzzy Definition of “Manage”

我前面提到ViewController管理View的层次结构;ViewController有一个叫view的属性,并且通过IBOutlets可以访问该view的所有子view。这样扩展性不好尤其是有很多outlet的时候。有些时候,你会更倾向用子ViewController来帮助管理诸多的子view。

究竟什么时候时候把ViewController分解是有益的?验证用户输入的逻辑是放在Controller呢?还是Model?

有太多模棱两可的代码需要写在某个地方,但是又没人能够给个准确的说法。似乎看起来无论把这些代码写在哪里,View和ViewController都是紧紧的耦合着。好吧,你就让它们合体吧!

嘿!现在有一个新的想法......

Model-View-ViewModel

在理想情况下,MVC也可以运行良好。然而,现实情况并非如此。前面已经详细分解了MVC的典型使用场景和弊端,现在我们来看看另外一种选择:Model-View-ViewModel。

MVVM来源于微软,但并非局限于微软体系。MVVM和MVC非常相似。它减弱了View和Controller之间的耦合并且引出了一个新的组件。

MVVM
MVVM

在MVVM的世界里,View和ViewController紧密的结合,我们完全可以把它们看成一部分。View还是没有Model的引用,Controller也是如此。作为替代,它们持有ViewModel的引用。

ViewModel是一个放置用户输入验证逻辑、View展现逻辑、网络请求发送以及其它五花八门代码的绝佳场所。ViewModel绝不能包含的一个东西就是View本身。ViewModel中的逻辑既可以用于iOS,也可以用于OS X。(换句话说,不要在ViewModel中引用#import UIKit.h,这样就对了。)

既然展现层逻辑,比如Model里面的值映射到格式化的字符串被包含在ViewModel中,那么ViewController就不再那么那么的膨胀了。当你开始使用MVVM,一个最大的收获就是一开始你只需要在ViewModel中放一点点逻辑的代码,然后随着对这种模式的适应,可以慢慢的把相关代码迁移到ViewModel中去。

使用MVVM写的代码比较容易被测试,因为ViewModel包含了所有表现层的逻辑而且没有引用View,所以可以通过编写测试代码进行完整的测试。经管测试Core Data的Models会包含很多坑,但是只要用了MVVM,就能保证任何一个unit都能被测试到。

根据我自身的经验,使用MVVM的一个结果就是代码量会有明显的增长,但是代码的复杂度大大降低。这是一个明治的选择。

如果你再看一遍MVVM的图,你会发现我使用了两个容易产生混淆的词:“notify” 和 “update”,但没有具体说清楚。你可以使用KVO,就像MVC中一样,但这样很快就会难以管理。在实践中,使用ReactiveCocoa把各个部分粘合起来是很棒的一个方式。

想要知道更多关于怎么结合使用MVVM和ReactiveCocoa吗?就请阅读Colin Wheeler的高质量文章或者获取我写的开源应用。你也可以阅读我写的关于ReactiveCocoa和MVVM的书。


    0 0
    原创粉丝点击