iOS基于MVC模式重构项目

项目到一段落了,终于将要完成1.X.X的最后版本即将进入2.0.1,但是看之前的项目全都是Controller(View是storyboard实现).数据和Controller糅杂在一起,于是使用MVC模式重构项目,刚好看到这篇文章,拷贝下方便自己以后看.


iOS中的MVC和MVP

Cocoa版本的MVC

根据官网上的描述, Cocoa中的MVC是这样的:


Model Objects Encapsulate Data and Basic Behaviors

View Objects Present Information to the User

Controller Objects Tie the Model to the View

C和P的差别

通过搜索引擎,发现其实MVP其实两种的:

1.Passive View

2.Supervising Controller

网上绝大部分谈论MVP的文章谈论的其实都是Passive View,这里放上一张Passive View的示意图:


乍一看上去是不是和MVC一样?我也一直一头雾水,直到在stackoverflow看到这个答案:

MVP: the view is in charge. The view, in most cases, creates its presenter. The presenter will interact with the model and manipulate the view through an interface. The view will sometimes interact with the presenter, usually through some interface. This comes down to implementation; do you want the view to call methods on the presenter or do you want the view to have events the presenter listens to? It boils down to this: The view knows about the presenter. The view delegates to the presenter.

MVC: the controller is in charge. The controller is created or accessed based on some event/request. The controller then creates the appropriate view and interacts with the model to further configure the view. It boils down to: the controller creates and manages the view; the view is slave to the controller. The view does not know about the controller.

简而言之,MVP是View驱动的,View层持有一个对应Presenter的引用,View上的交互事件首先会调用Presenter提供的接口,然后Presenter调用Model提供的方法取得数据,最后Presenter将取得的数据传递到View上展示.

MVC则是由Controller驱动的,Controller持有View,并响应View上的交互事件,根据交互调用不同的Model方法取得反馈数据,再将数据传递给View展示.(本人的项目重构就是这种,由于使用storyboard,View上的控件在C中初始化,M中放处理数据的方法和变量)

我的理解是,MVP是用户视角:所见即View.MVC则是程序员视角:I control everyone.

理解MVC和MVP的差别困惑的地方在于,UIViewController到底是属于C(P)层还是V层呢?下面将分别具体分析一下这两种观点.

观点一:UIViewController的归属--->View

如果把UIViewController视为V层,即上面MVP示意图中的Passive View,那么UIViewController将只负责View布局相关逻辑,不涉及任何与Model层的交互!!

不涉及任何与Model层的交互!!

不涉及任何与Model层的交互!!

所有的业务逻辑交互通过UIViewController持有的Presenter与Model间的调用来完成.

观点二:UIViewController的归属--->Controller

那如果把UIViewController视为C层,从MVC设计理念上来说,C层不会负责具体View的布局及展示逻辑,但是由于iOS中UIViewController的特殊设计,导致很多开发者直接就在UIViewController包含了很多具体布局相关的代码,更可怕的情况是不止是View的初始化,包括网络请求及具体业务处理也被包含到UIViewController中,这也许就是有人戏称MVC为:MassiveViewController的原因.

Model,DataInfo以及对胖瘦Model的争议

Model和DataInfo的差异

MVC架构思想中更倾向于Model是一个Layer,而不是一个Object(Java或Android中的bean).

所以这里的DataInfo我将其定义为一个DTO(Data Transfer Objec),更简单的来说,就是一个数据结构,跟我们在学校学习C语言时写的一个student结构体基本是一个意思.

我的理解是,Model是用来处理业务逻辑的,可视为传统开发三层架构中的BLL(Bussiness Logic Layer)和DAL(Data Access Layer)的合体,负责所有的具体业务.比如,对一个包含安全认证的App,你可能需要一个AuthModel来负责所有的认证逻辑以及认证信息的本地持久化.这样,Controller中只需要调用AuthModel提供的接口便能完成相应安全认证功能,职责分明.

Model中的一些方法的产出DTO,用来更新View.例如UserModel会去查询用户信息,然后将服务器返回的用户信息转换成一个本地的UserInfo,将这个UserInfo传递给View进行展示.

因此业界关于胖瘦Model的争议,我更多的理解是对于DataInfo是否需要提供涉及到业务的扩展功能的争议.

业务场景举例

举例一个业务场景,一个用户信息View上需要展示用户性别,一般来说服务器只会在返回的用户信息中包含一个sex字段(这里用0代表女性,1代表男性),需要使用的时候可能需要使用if语句进行判断然后输出不同的性别文字或图片.

从个人习惯上来说,很多开发者会将服务器返回的用户信息转换成一个本地的UserInfo DTO,然后将这个DTO传递给要对应需要展示的View,然后在View中进行输出判断.

当然,开发者可能会使用现在很流行的一些字典转模型框架(YYModel,MJextension等),也可以使用这些框架提供的配置接口在转换时就实现这种输出逻辑的转换,或者直接在UerInfo sex属性的getter方法中进行转换后输出.

不管怎样,只要在DataInfo这个层级上做了类似的这些转换,那么业务逻辑就已经侵入了DTO的定义.

一种解决思路

但是,这种场景几乎又是不可避免的,如何解决呢?被误解的 MVC 和被神化的 MVVM提出了一种借鉴MVVM的解决思路,具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程.这样抽象之后,View 只接受 ViewModel,而 Controller 只需要传递 ViewModel 这么一行代码。而另外构造 ViewModel 的过程,我们就可以移动到另外的类中了.

在我看来,这样ViewModel层其实只是把上文中对DataInfo的扩展单独提炼了出来.这样就将View层完全与业务逻辑完全隔离开.

本次基于MVC的项目重构步骤

思考要解决的问题

回到项目重构的问题上来,我认为项目重构首先要想清楚的问题:

项目层级如何划分?

大的业务场景有哪些?

将UIViewController归类为View还是Controller?

谁来负责网络请求,Model还是Controller? (我也希望大神给个建议,本人的在C中,)

从Model中取得数据后Controller怎么把数据传递给View去展示?按照View层级逐级传递?是否需要使用ViewModel?

Model的生命周期怎么控制?(全局Model和私有Model的划分)

View层级结构越来越深时,怎么传递用户的交互操作?(毫无疑问NSNotification)

UIViewController的划分

本次重构中还是将UIViewController归类为C层,但是为了将UIViewController彻底和UIView分离开,命名上我们直接使用XXXController,我们对每一个XXXController设计了一个对应的名为的XXXContainerView的UIView对象,所有的view布局都会在这个XXXContainerView中完成.

项目目录结构

重构后的项目目录结构如下:


业务场景划分

由于之前赶进度开发,没有做具体的功能拆分.本次重构之前使用了StartUML绘制了主要场景下的UML图,包括类图,时序图,流程图.

强烈推荐新项目正式编码之前就完成这一步,并由前后端技术负责人主持进行UML评审.所有涉及到的业务Model的property以及public方法,所有DataInfo类的属性,甚至所有的Controller都会在绘制UML的过程中产出.当然,要想绘制所有场景下的UML图可能耗时比较久,一般来说只要绘制出主要交互场景即可.

网络请求

在手机端基本上所有的网络请求都是跟业务挂钩的,显然放在Model里更合适.至于请求完成后的回调就看个人习惯了,delegate或者block都是可行的.

后续

由于时间原因,并没有写出具体的示例代码,之后会针对一个示例场景,写出我理解中的各个MV(X)模式的参考代码,期待成文后与同行探讨.

MVVM扩展

放一张MVVM的示意图:

看上去是不是很像MVP?只是多了View和ViewModel的双向绑定,这里强调一点,RAC不一定登陆MVVM,MVVM也不一定要使用RAC.

(转自: 长剑废水 如侵权,望告知) 

关于MVC的争论已经有很多,对此我的观点是:对于iOS开发中的绝大部分场景来说,MVC本身是没有问题的,你认为的MVC的问题,一定是你自己理解的问题(资深架构师请自动忽略本文).

行文过程中查阅了互联网上的大量文档,其中水平良莠不齐(最常见的就是MVC改个名就当MVVM的),当然也有许多非常有价值的参考资料,在文末会逐一列举,以供参考.

iOS中的MVC和MVP

Cocoa版本的MVC

根据官网上的描述, Cocoa中的MVC是这样的:

blob.png

Model Objects Encapsulate Data and Basic Behaviors

View Objects Present Information to the User

Controller Objects Tie the Model to the View

C和P的差别

通过搜索引擎,发现其实MVP其实两种的:

Passive View

Supervising Controller

网上绝对部分谈论MVP的文章谈论的其实都是Passive View,这里放上一张Passive View的示意图:

blob.png

乍一看上去是不是和MVC一样?我也一直一头雾水,直到在stackoverflow看到这个答案:

MVP: the view is in charge. The view, in most cases, creates its presenter. The presenter will interact with the model and manipulate the view through an interface. The view will sometimes interact with the presenter, usually through some interface. This comes down to implementation; do you want the view to call methods on the presenter or do you want the view to have events the presenter listens to? It boils down to this: The view knows about the presenter. The view delegates to the presenter.

MVC: the controller is in charge. The controller is created or accessed based on some event/request. The controller then creates the appropriate view and interacts with the model to further configure the view. It boils down to: the controller creates and manages the view; the view is slave to the controller. The view does not know about the controller.

简而言之,MVP是View驱动的,View层持有一个对应Presenter的引用,View上的交互事件首先会调用Presenter提供的接口,然后Presenter调用Model提供的方法取得数据,最后Presenter将取得的数据传递到View上展示.

MVC则是由Controller驱动的,Controller持有View,并响应View上的交互事件,根据交互调用不同的Model方法取得反馈数据,再将数据传递给View展示.

我的理解是,MVP是用户视角:所见即View.MVC则是程序员视角:I control everyone.

理解MVC和MVP的差别困惑的地方在于,UIViewController到底是属于C(P)层还是V层呢?下面将分别具体分析一下这两种观点.

观点一:UIViewController的归属--->View

如果把UIViewController视为V层,即上面MVP示意图中的Passive View,那么UIViewController将只负责View布局相关逻辑,不涉及任何与Model层的交互!!

不涉及任何与Model层的交互!!

不涉及任何与Model层的交互!!

所有的业务逻辑交互通过UIViewController持有的Presenter与Model间的调用来完成.

观点二:UIViewController的归属--->Controller

那如果把UIViewController视为C层,从MVC设计理念上来说,C层不会负责具体View的布局及展示逻辑,但是由于iOS中UIViewController的特殊设计,导致很多开发者直接就在UIViewController包含了很多具体布局相关的代码,更可怕的情况是不止是View的初始化,包括网络请求及具体业务处理也被包含到UIViewController中,这也许就是有人戏称MVC为:MassiveViewController的原因.

Model,DataInfo以及对胖瘦Model的争议

Model和DataInfo的差异

MVC架构思想中更倾向于Model是一个Layer,而不是一个Object(Java或Android中的bean).

所以这里的DataInfo我将其定义为一个DTO(Data Transfer Objec),更简单的来说,就是一个数据结构,跟我们在学校学习C语言时写的一个student结构体基本是一个意思.

我的理解是,Model是用来处理业务逻辑的,可视为传统开发三层架构中的BLL(Bussiness Logic Layer)和DAL(Data Access Layer)的合体,负责所有的具体业务.比如,对一个包含安全认证的App,你可能需要一个AuthModel来负责所有的认证逻辑以及认证信息的本地持久化.这样,Controller中只需要调用AuthModel提供的接口便能完成相应安全认证功能,职责分明.

Model中的一些方法的产出DTO,用来更新View.例如UserModel会去查询用户信息,然后将服务器返回的用户信息转换成一个本地的UserInfo,将这个UserInfo传递给View进行展示.

因此业界关于胖瘦Model的争议,我更多的理解是对于DataInfo是否需要提供涉及到业务的扩展功能的争议.

业务场景举例

举例一个业务场景,一个用户信息View上需要展示用户性别,一般来说服务器只会在返回的用户信息中包含一个sex字段(这里用0代表女性,1代表男性),需要使用的时候可能需要使用if语句进行判断然后输出不同的性别文字或图片.

从个人习惯上来说,很多开发者会将服务器返回的用户信息转换成一个本地的UserInfo DTO,然后将这个DTO传递给要对应需要展示的View,然后在View中进行输出判断.

当然,开发者可能会使用现在很流行的一些字典转模型框架(YYModel,MJextension等),也可以使用这些框架提供的配置接口在转换时就实现这种输出逻辑的转换,或者直接在UerInfo sex属性的getter方法中进行转换后输出.

不管怎样,只要在DataInfo这个层级上做了类似的这些转换,那么业务逻辑就已经侵入了DTO的定义.

一种解决思路

但是,这种场景几乎又是不可避免的,如何解决呢?被误解的 MVC 和被神化的 MVVM提出了一种借鉴MVVM的解决思路,具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程.这样抽象之后,View 只接受 ViewModel,而 Controller 只需要传递 ViewModel 这么一行代码。而另外构造 ViewModel 的过程,我们就可以移动到另外的类中了.

在我看来,这样ViewModel层其实只是把上文中对DataInfo的扩展单独提炼了出来.这样就将View层完全与业务逻辑完全隔离开.

本次基于MVC的项目重构步骤

思考要解决的问题

回到项目重构的问题上来,我认为项目重构首先要想清楚的问题:

项目层级如何划分?

大的业务场景有哪些?

将UIViewController归类为View还是Controller?

谁来负责网络请求,Model还是Controller?

从Model中取得数据后Controller怎么把数据传递给View去展示?按照View层级逐级传递?是否需要使用ViewModel?

Model的生命周期怎么控制?(全局Model和私有Model的划分)

View层级结构越来越深时,怎么传递用户的交互操作?(毫无疑问NSNotification)

UIViewController的划分

本次重构中还是将UIViewController归类为C层,但是为了将UIViewController彻底和UIView分离开,命名上我们直接使用XXXController,我们对每一个XXXController设计了一个对应的名为的XXXContainerView的UIView对象,所有的view布局都会在这个XXXContainerView中完成.

项目目录结构

重构后的项目目录结构如下:

一级目录 子目录 目录说明

Macro

存放开发过程中所需的一些宏定义

Category

存放不涉及业务,用来辅助开发的分类

Tools 不同的业务工具类 存放涉及轻量级业务的处理类,比如根据不同业务格式化输出不同的字符串

Views 不同的业务模块页面名 存放不同业务模块页面下的V

Controllers 不同的业务模块页面名 存放不同业务模块页面下的C

ViewModels 不同数据模块名 数据翻译层,将DataInfo数据翻译为View可直接展示的数据,但本次重构中由于时间因素不强制使用

Model PublicModel 公用的全局Model,比如用户信息UserModel

MoudleModel 单独某个模块使用的私有Model,只负责私有业务

Services 不同的Service 提供底层服务,例如HttpService,SecurityService

业务场景划分

由于之前赶进度开发,没有做具体的功能拆分.本次重构之前使用了StartUML绘制了主要场景下的UML图,包括类图,时序图,流程图.

强烈推荐新项目正式编码之前就完成这一步,并由前后端技术负责人主持进行UML评审.所有涉及到的业务Model的property以及public方法,所有DataInfo类的属性,甚至所有的Controller都会在绘制UML的过程中产出.当然,要想绘制所有场景下的UML图可能耗时比较久,一般来说只要绘制出主要交互场景即可.

网络请求

在手机端基本上所有的网络请求都是跟业务挂钩的,显然放在Model里更合适.至于请求完成后的回调就看个人习惯了,delegate或者block都是可行的.

后续

由于时间原因,并没有写出具体的示例代码,之后会针对一个示例场景,写出我理解中的各个MV(X)模式的参考代码,期待成文后与同行探讨.

MVVM扩展

放一张MVVM的示意图:

blob.png

看上去是不是很像MVP?只是多了View和ViewModel的双向绑定,这里强调一点,RAC不一定登陆MVVM,MVVM也不一定要使用RAC.

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

推荐阅读更多精彩内容