iOS架构补完计划--浅谈MVC及其衍生架构模式(附简易图解)

目录

概述

傻瓜图解

MVC

一个正统的MVC、三者的任务是什么?

关于View到底该不该写一些业务代码

胖Model与瘦Model

强业务、弱业务

胖Model

瘦Model

该用哪个?

MVVM

Model

View

ViewModel

Controller

ReactiveCocoa对于MVVM的意义是什么?

MVCS

MVP

VIPER

关于架构设计、一些观点

控制好Controller的代码量

对于MVX如何选择

无论用哪种模式、都要深刻的理解每个模块不同的职责

概述

其实只要是架构上的设计、本质上都是三个角色:数据管理者、数据加工者、数据展示者。

不管是MVC、MVVM、MVP、VIPER或者任何新的设计模式、都跳不出这三个角色。无非是把数据管理者的工作进行拆分、唯一的界定标准就是把工作拆分的粒度大小。

而无论哪种思想、最终都逃不开三个问题的取舍。代码量、通用性、可读性。

这里我主要写的是MVC和MVVM、对于其他的架构只是略微提及。

傻瓜图解

这两天总有人跟我说我想看Demo...我想了想觉得Demo其实也不太直观、干脆画几个图好了。

主要是想表达每个模块里应该放什么类型的东西、线可能有连得不对的地儿。亲们尽量意会吧。

MVC

MVC--胖Model

MVC--瘦Model

MVC--胖瘦结合

MVVM

MVP

VIPER

试了试...感觉画着费劲。能理解了前面几个的话、看文字应该也能理解VIPER吧。

MVC

MVC就是典型的着重通用型与可读性、这正是一个作为万物之初的架构所需要保证的事。简单、易学。

Model进行数据管理

View进行数据展示

Controller负责根据需求对Model以及View进行调配。

不过和广义的MVC不同、客户端(别的我不知道啊、起码iOS)由于UIViewController自带一个容器View、所以除了上述的正统任务之外、Controller还需要承担View的生成、布局等的任务。

一个正统的MVC、三者的任务是什么?

所以、我们可以将MVC三者的任务再进一步细化一下

Model:

为Controller的读取提供数据

为Controller的写入提供接口

为Controller提供基本的业务组件

最常用的就是网络请求之后Json转Model、写入数据库之前Model转Json。

View:

界面的展示

响应与业务无关的事件(动画效果、点击反馈、点击事件的开关保护等等)

何为业务事件:Model数据的改变、网络请求的发送、页面的跳转、页面的刷新等等。

Controller

管理self.view的生命周期

负责生成所有的View实例、以及布局。

将恰当的Model交付给View展示。

监听来自View与业务有关的事件、通过与Model的合作、来完成对应事件的业务。

关于View到底该不该写一些业务代码

其实自己以前。也会图方便、把一些自认为的弱业务写在View里。

举个例子:

一个Cell、有用户Nickname、还有一个用户头像的Button。

点击事件肯定由Cell捕获。这个时候跳转用户主页的动作、该由View完成、还是传递给Controller?

假设我们交由View、也就是当前的Cell跳转了。很方便、省去了写代理的小十行代码。

并且这个Cell也可以挪到其他页面去使用、一样能跳到用户主页、又省去不少代码。

有一天、产品让你在某个页面点击头像不执行任何动作。

咋办呢?机智如你、给这个Cell添加了一个bool值来控制是否跳转就好了。

又过了几天、产品让你在某个页面把这个头像弄成点击之后弹出举报框。

这怎么搞呢?也不是不行、你又给这个Cell添了一个枚举的type。这简直完美、不同的Type执行不同的事件、你顺便取消了那个bool、把他也写成了一个type。

又过了一阵子、产品又告诉你当满足某些条件的时候、这个头像跳主页。另一些条件的时候、这个头像不能跳。

于是、你终于写了个block或者代理、在点击之后执行一下再看下一步怎么跳转。

此时、回过头来再看你的Cell、已经面目全非了、充斥着各种业务判断。

可能你会说、如果产品真的这么二逼。那我干脆再copy一个Cell就好了啊。

但是别忘了、你当初这么设计这个Cell的时候可是为了节省下页面跳转的小十行代码、而你现在却要为此付出copy一整个Cell的代价。

其实还有一个更重要的问题、就是你这个View的模块复用基本为0

假设你需要另起一个新的工程写一个demo、如果用这个View你首先要解决一大堆跳转代码上Controller 文件的缺失、然后还会发现、原来写的很多逻辑、type在这个demo里毫无用处。挨个删除、梳理逻辑又要耗费很多时间。

而这些将来会发生的问题、如果你最开始不把业务事件代码硬写进View里、一件都不会发生。

胖Model与瘦Model

这里先要引出两个概念。强业务、弱业务。

二者关键的区别是代码变动的频率大小与涉及模块的多少。举两个例子:

1、比如把时间戳转化、小数点的格式化或者修改A属性进行一系列计算并且改变B属性、这种业务就属于弱业务。

2、再比如一个一个订单Model的确认收货、就应该归入没办法归入弱业务、因为涉及网络请求、加密等等多个底层模块。

胖Model

主旨是Controller从Model里拿到的数据、不需要进行更多的判断、处理等操作、就能使用。举个例子:

Raw Data:    timestamp:1234567FatModel:@property(nonatomic,assign)CGFloattimestamp;    - (NSString*)ymdDateString;// 2015-04-20 15:16- (NSString*)gapString;// 3分钟前、1小时前、一天前、2015-3-13 12:34Controller:self.dateLabel.text = [FatModel ymdDateString];self.gapLabel.text = [FatModel gapString];

这就需要将弱业务、写进Model、很好的满足的复用的需求。

胖Model也是存在问题的、就是移植的困难。毕竟业务再弱、也是代码、当项目成长到一定程度、这个Model也将会变得相当的臃肿。

瘦Model

就是要把MVC的M贯彻倒底、除了业务的表达啥都不管。

但是这样又会导致Controller中的代码变得异常臃肿(废话么、连时间戳转化都要交给Controller不肿才怪)

所以瘦Model要借助一些外来的辅助模块(索性可以叫Helper)来对弱业务做抽象。举个例子:

Raw Data:{"name":"casa","sex":"male",}SlimModel:@property(nonatomic,strong)NSString*name;@property(nonatomic,strong)NSString*sex;Helper:#define Male 1;#define Female 0;+ (BOOL)sexWithString:(NSString*)sex;Controller:if([Helper sexWithString:SlimModel.sex] == Male) {        ...    }

该用哪个?

我个人用胖Model用的比较多、但是也借助了一些瘦Model的思想。举例来讲:

除了上述很明确的可以放到Model里的弱业务之外、像一个订单中的确认收货、发货、申请退货等等操作、他们既不算特别强的业务、而且还有很高的复用需求(订单列表和订单详情都需要确认收货)。

这种业务有一种特点、就是代码就在哪里。不管你放到哪、都只能挪不能删。但挪到哪、都不完全合适。从定义上来讲十分莫若两可。个人觉得:

这种的业务:能不放在Controller里就不要放

你可以干脆放到胖Model里、毕竟将来拆分一个400行的Model、比拆分一个1400行的Controller容易得多。

你也可以单独新建一个Helper、配合着Model来完成业务。这样想移植页面就单用Model、想带业务移植就带着Helper(其实这个思路已经很接近MVP了、但是还差提点。MVP还需要为View提供数据)。

MVVM

弱弱的一说、我并不推荐iOS中写MVVM(因为入侵性实在太强)、不会教你怎么用RAC怎么写出MVVM、只是想让你理解什么是MVVM

MVVM现在已经是一种非常成熟的思想了。应用也十分普及、例如Vue以及小程序。

MVVM的初衷也是为了Controller减负。

刚才的胖Model只从Controller移植走了一些简单的弱业务。

而ViewModel则干脆把数据的处理全部从Controller移植了出去。

理想上相同的输入(比如网络服务响应)将会导出相同的输出(属性的值)。

简单的说一下M、V、VM的在架构中所扮演的角色。

Model:

和正统MVC中的瘦Model一样、只承载最基本的数据单元。

@interfaceUserListModel:NSObject@property(nonatomic,strong,readonly)NSString*userName;@property(nonatomic,strong,readonly)UIImage*portraitImg;@end

View

其实也和正统的MVC一样、只做展示工作、不承接任何业务逻辑。

但是需要注意的是、有时候也会在View中将ViewModel与View做一些绑定工作(ViewModel本质上也算是Model层、所以View并不适合直接持有ViewModel)。

- (void) awakeFromNib {    [superawakeFromNib];    RAC(self. portraitImgView,  image) = RACObserve(self,  viewModel. portraitImg);    RAC(self. userNameLabel,  text) = RACObserve(self,  viewModel. userName);}

ViewModel

提供了这个页面展示所有需要的数据的一个对象。

举一个简单的例子:

@interfaceUserListViewModel:NSObject@property(nonatomic,assign,readonly)BOOLloading;@property(nonatomic,strong,readonly)NSArray*userList;@property(nonatomic,strong,readwrite)NSString*searchUserName;  - (void) searchUser;- (void) deleteUserWithModel:(UserListModel *)model;- (void) loadMoreUser;

这个ViewModel里涵盖了所有页面展示需要的要素。用户列表、搜索名称、是否需要显示网络加载的小菊花。

并且涵盖了对这些数据的所有操作方法。加载更多、搜索、删除。

但是、ViewModel到底也是一个Model层、不应该引入UIKit(View层)。如果删除需要弹窗、那么这个弹窗动作是不应该交给ViewModel来搞的、因为这已经不属于数据处理的范畴了

仔细想想、这个ViewModel其实就是把Controller中与页面相关的数据处理代码挪进来了而已。

如此、我们设置可以脱离View层。拿着这个ViewModel去跑单元测试。简直碉堡。

Controller

虽然MVVM中没有体现出C的字眼、但是实际操作肯定是要遵循着View <-> C <-> ViewModel <-> Model。

起码在iOS中是、这和Vue中简单粗暴的方式不同:

//页面里

{{message}}

      {{value}}

//js文件里newVue({//数据data:{            key:'welcome vue',            arr:['apple','banana','orange','pear'],            json:{a:'apple',b:'banana',c:'orange'}      }//方法methods:{            add:function(){//push 添加元素this.arr.push('tomato');            }      }})

因为Html中并没有明确的Controller的概念、整个Html文件就是Controller容器。

和iOS的区别很明显:

除去精炼的写法、整个Html文件所关联的js资源都可以无障碍互通、所以View层无时无刻不持有着Model层、在View层直接绑定更方便。

所以iOS中Controller的作用就显而易见了

Controller夹在View和ViewModel之间做的其中一个主要事情就是将View和ViewModel进行绑定。在逻辑上、Controller知道应当展示哪个View、Controller也知道应当使用哪个ViewModel、然而View和ViewModel它们之间是互相不知道的、所以Controller就负责控制他们的绑定关系。

ReactiveCocoa对于MVVM的意义是什么?

ReactiveCocoa并不是MVVM思想的根本、不用ReactiveCocoa也能MVVM、用ReactiveCocoa能更好地体现MVVM的精髓。

我一直强调MVC中的M与V是应该尽量不要互相持有的。

这个时候如何把原本松散的二者通过C紧密的联系起来、就要进行数据绑定。

而这种数据绑定、iOS本身并没有什么太靠谱的办法(就像刚才前端例子中的

{{message}}

、这种写法)。

虽然KVO、Notification、block、delegate和target-action都可以用来做数据通信进而实现绑定

但都不如ReactiveCocoa来的《《《优雅》》》。

对、这就是我开始说为什么RAC对于MVVM不是必须的。

如果不用ReactiveCocoa、绑定关系可能就做不到那么松散那么好、但并不影响它还是MVVM。

MVCS

将数据持久化的代码移植给了store...

MVP

实际上就是将Controller中关于Model与View的调配处理的代码移植了过来。各部分分工如下:

View

负责界面展示和布局管理、向Presenter暴露视图更新和数据获取的接口

Presenter

负责接收来自View的事件、通过View提供的接口更新视图,并管理Model。

Model

和MVC中的一样,提供数据模型

VIPER

除了View没拆、其它的都拆了....

在MVP的基础上新增了Interactor与Router

View

提供完整的视图。负责视图的组合、布局、更新

向Presenter提供更新视图的接口

将View相关的事件发送给Presenter

Interactor

维护主要的业务逻辑功能,向Presenter提供现有的业务用例

维护、获取、更新Entity

当有业务相关的事件发生时、处理事件、并通知Presenter

Presenter

接收并处理来自View的事件

向Interactor请求调用业务逻辑

向Interactor提供View中的数据

接收并处理来自Interactor的数据回调事件

通知View进行更新操作

通过Router跳转到其他View

Entity

和Model一样的数据模型

Router

提供View之间的跳转功能、减少了模块间的耦合

初始化VIPER的各个模块

VIPER与其他的架构相比最大的优势就是粒度简直细化成了尘埃、极大的提高了可测性。

但问题也相当显著、层级越多、数据传递的工作量(API)就越大。文件越多、新建一个页面的成本也就越高。

关于架构设计、一些观点

除了MVVM、它对iOS的入侵性简直太高、主要取决于团队的决策(比如是不是新项目、Leader是不是想玩玩看)。

控制好Controller的代码量

随着项目的进行、代码量最多只能优化、膨胀不可避免。

而在没办法继续精简的前提下、想控制Controller的代码量。就要在可读性和通用性之间进行取舍。该挪走的时候就挪走吧、毕竟梳理一个单独的模块、比梳理一个几千行的Controller要方便多了。

对于MVX如何选择

其实完全要看业务的性质以及复杂度。

如果你一个页面只有一个UITableView、搞出一些奇淫技巧其实意义  不大、徒增烦恼。踏踏实实用MVC对大家都好。

如果感觉业务里有非常多的View与Model互通、或者需大量复用、可以用MVP。

如果有大量的数据读写、可以用MVCS。

如果业务相当的复杂、耦合让人浑身难受。做好模块化或者干脆VIPER才是出路。

无论用哪种模式、都要深刻的理解每个模块不同的职责

比如MVVM里的VM、既然是Model层、就不要把UIKit放进去。

再比如MVP中的P、既然是为了帮助Controller协调M与V而生、就不要把与二者无关的工作也抢过来干。

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

推荐阅读更多精彩内容