iOS架构补完计划--浅谈架构模式(MVC/MVVM)
点击上方“iOS开发”,选择“置顶公众号”
关键时刻,第一时间送达!
目录
概述
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、对于其他的架构只是略微提及。
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
这里先要引出两个概念。强业务、弱业务。
二者关键的区别是代码变动的频率大小与涉及模块的多少。举两个例子:
比如把时间戳转化、小数点的格式化或者修改A属性进行一系列计算并且改变B属性、这种业务就属于弱业务。
再比如一个一个订单Model的确认收货、就应该归入没办法归入弱业务、因为涉及网络请求、加密等等多个底层模块。
胖Model
主旨是Controller从Model里拿到的数据、不需要进行更多的判断、处理等操作、就能使用。举个例子:
Raw Data:
timestamp:1234567
FatModel:
@property (nonatomic, assign) CGFloat timestamp;
- (NSString *)ymdDateString; // 2015-04-20 15:16
- (NSString *)gapString; // 3分钟前、1小时前、一天前、2015-3-13 12:34
Controller:
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)、而且还有很高的复用需求(订单列表和订单详情都需要确认收货)。
这种业务有一种特点、就是代码就在哪里。不管你放到哪、都只能挪不能删。但挪到哪、都不完全合适。从定义上来讲十分莫若两可。个人觉得:
这种的业务:能不放在Controller里就不要放
你可以干脆放到胖Model里、毕竟将来拆分一个400行的Model、比拆分一个1400行的Controller容易得多。
你也可以单独新建一个Helper、配合着Model来完成业务。这样想移植页面就单用Model、想带业务移植就带着Helper(这个思路已经很接近MVVM了、但实际上并不是。因为VM严格意义上处理的是页面相关数据、而不是具体业务)。
MVVM
弱弱的一说、我并不推荐iOS中写MVVM(因为入侵性实在太强)、不会教你怎么用RAC怎么写出MVVM、只是想让你理解什么是MVVM
MVVM现在已经是一种非常成熟的思想了。应用也十分普及、例如Vue以及小程序。
MVVM的初衷也是为了Controller减负。
刚才的胖Model只从Controller移植走了一些简单的弱业务。
而ViewModel则干脆把数据的处理全部从Controller移植了出去。
理想上相同的输入(比如网络服务响应)将会导出相同的输出(属性的值)。
简单的说一下M、V、VM的在架构中所扮演的角色。
Model:
和正统MVC中的瘦Model一样、只承载最基本的数据单元。
@interface UserListModel: 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 {
[super awakeFromNib];
RAC(self. portraitImgView, image) = RACObserve(self, viewModel. portraitImg);
RAC(self. userNameLabel, text) = RACObserve(self, viewModel. userName);
}
ViewModel
提供了这个页面展示所有需要的数据的一个对象。
举一个简单的例子:
@interface UserListViewModel: NSObject
@property (nonatomic, assign, readonly) BOOL loading;
@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}}
for="value in arr">
{{value}}
//js文件里
new Vue({
//数据
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的入侵性简直太高、主要取决于团队的决策(比如是不是新项目、是不是想玩玩看)。
控制好Controller的代码量
随着项目的进行、代码量最多只能优化、膨胀不可避免。
而在没办法继续精简的前提下、想控制Controller的代码量。就要在可读性和通用性之间进行取舍。该挪走的时候就挪走吧、毕竟梳理一个单独的模块、比梳理一个几千行的Controller要方便多了。
对于MVX如何选择
其实完全要看业务的性质以及复杂度。
如果你一个页面只有一个UITableView、搞出一些奇淫技巧其实意义 不大、徒增烦恼。踏踏实实用MVC对大家都好。
如果感觉业务里有非常多的View与Model互通、可以用MVP。
如果有大量的数据读写、可以用MVCS。
如果业务相当的复杂、耦合让人浑身难受。做好模块化或者干脆VIPER才是出路。
无论用哪种模式、都要深刻的理解每个模块不同的职责
比如MVVM里的VM、既然是Model层、就不要把UIKit放进去。
再比如MVP中的P、既然是为了帮助Controller协调M与V而生、就不要把与二者无关的工作也抢过来干。
作者:kirito_song
链接:https://www.jianshu.com/p/004075a47ed6?utm_source=desktop&utm_medium=timeline
iOS开发整理发布,转载请联系作者授权
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 习近平G20里约峰会展现大国担当 7981419
- 2 多国驻乌克兰大使馆因袭击风险关闭 7901931
- 3 78岁老太将减持2.5亿股股票 7822614
- 4 二十国集团里约峰会将会卓有成效 7747882
- 5 俄导弹击中乌水电站大坝 7627216
- 6 孙颖莎王艺迪不敌日本削球组合 7553981
- 7 高三女生酒后被强奸致死?检方回应 7462288
- 8 第一视角记录虎鲨吞下手机全程 7321127
- 9 手机不能看医院CT图像就要少收费 7214532
- 10 智慧乌镇点亮数字经济新未来 7191393