转载

iOS架构模式——MV(X)的理解与实战

作为一个iOS程序员,MVC一定是我们耳熟能详的一种架构模式,而且当你的项目规模不大的时候,MVC也确实有它的优势,它的开发效率确实是足够高。但当你的项目发展的一定的规模,你会发现传统的MVC模式会导致C层代码量剧增,维护困难等一系列问题,这个时候我们就需要考虑一些其它模式了。

MV(X)的基本要素

常用的架构模式

  • MVC

  • MVVM

  • MVP

  • VIPER

前面三种模式都由三个模块组成:

  • Models —— 数据层,负责数据的处理。

  • Views —— 展示层,即所有的UI

  • Controller/Presenter/ViewModele(控制器/展示器/视图模型)——它们负责View与Mode之间的调配

MVC

传统的MVC

我们所熟知的MVC其实Apple给我们提供的Cocoa MVC,但其实MVC最先产生于Web,它原来的样子应该是这样的

iOS架构模式——MV(X)的理解与实战

传统MVC

在这种架构下,View是无状态的,在Model变化的时候它只是简单的被Controller重绘,比如网页中你点击了一个新的链接,整个页面就重新加载。尽管这种MVC在iOS应该里面可以实现,但是由于MVC的三个模块都紧密耦合了,每一个模块都和其它两种模块有联系,所以即便是实现了也没有什么意义。这种耦合还降低了它们的可重用性,所以,传统的MVC在iOS中可以舍弃了。

Apple的MVC

iOS架构模式——MV(X)的理解与实战

Cocoa MVC

Apple提供的MVC中,View和Model之间是相互独立的,它们只通过Controller来相互联系。可惜的是Controller得重用性太差,因为我们一般都把冗杂的业务逻辑放在了Controller中。

现实中,我们的MVC一般是这样的

iOS架构模式——MV(X)的理解与实战

现实MVC

为什么会这样呢?主要还是因为我们的UIViewController它本身就拥有一个VIew,这个View是所有视图的根视图,而且View的生命周期也都由Controoler负责管理,所以View和Controller是很难做到相互独立的。虽然你可以把控制器里的一些业务逻辑和数据转换工作交给Model,但是你却没有办法将一些工作让View来分摊,因为View的主要职责只是将用户的操作行为交给Controller去处理而已。于是Controller最终就变成了所有东西的代理和数据源,甚至还有网络请求.....还有......所以我们写的Controller代码量一般都是非常大的,随着当业务需求的增加,Controller的代码量会一直增长,而相对来说View和Model的代码量就比较稳定,所以也有人把MVC叫做Massive View Controller,因为Controller确实显得有些臃肿。

在这里关于Model的划分,其实有一个胖Model和瘦Model之分,它们的差别主要就是把Controller的部分数据处理职责交给了胖Model。

胖Model(Fat Model):

胖Model包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不用做额外的操作或者只做非常少的操作就能将数据应用在View上。

FatModel做了这些弱业务之后,Controller可以变得相对skinny一点,它只需要关注强业务代码。而强业务变动的可能性要比弱业务大得多,弱业务相对稳定,所以弱业务塞给Model不会有太大问题。另一方面,弱业务重复出现的频率要大于强业务,对复用性要求更高,如果这部分业务写在Controller,会造成代码冗余,类似的代码会洒得到处都是,而且一旦弱业务有修改,你就会需要修改所有地方。如果塞到了Model中,就只需要改Model就够了。

但是胖Mpdel也不是就是没有缺点的,它的缺点就在于胖Model相对比较难移植,虽然只是包含弱业务,但是它毕竟也是业务,迁移的时候很容易拔出罗布带出泥,也就是说它耦合了它的业务。而且软件是会成长的,FatModel也很有可能随着软件的成长越来越Fat,最后难以维护。

瘦Model(Slim Model):

瘦Model只负责业务数据的表达,所有业务无论强弱一律人给Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,然后配套各种helper类或者方法来对弱业务做抽象,强业务依旧交给Controller。

由于Slim Model跟业务完全无关,它的数据可以交给任何一个能处理它数据的Helper或其他的对象,来完成业务。在代码迁移的时候独立性很强,很少会出现拔出萝卜带出泥的情况。另外,由于SlimModel只是数据表达,对它进行维护基本上是0成本,软件膨胀得再厉害,SlimModel也不会大到哪儿去。缺点就在于,Helper这种做法也不见得很好,由于Model的操作会出现在各种地方,SlimModel很容易出现代码重复,在一定程度上违背了DRY(Don’t Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出现代码膨胀。

综上所述,Cocoa MVC在各方面的表现如下:

  • 划分- View 和 Model 确实是实现了分离,但是 View 和 Controller 耦合的太 厉害

  • 可测性- 因为划分的不够清楚,所以能测的基本就只有 Model 而已

  • 易用- 相较于其他模式,它的代码量最少。而且基本上每个人都很熟悉它,即便是没太多经验的开发者也能维护。

MVP

iOS架构模式——MV(X)的理解与实战

MVP

看起来和Cocoa MVC很像,也确实很像。但是,在MVC中View和COntroller是紧密耦合的,而在MVP中,Presenter完全不关注ViewController的生命周期,而且View也能被简单mock出来,所以在Presenter里面基本没有什么布局相关的代码,它的职责只是通过数据和状态更新View。

而且在MVP中,UIVIewController的那些子类其实是属于View的。这样就提供了更好的可测性,只是开发速度会更高,因为你必须手动去创建数据和绑定事件。

下面我写了个简单的Demo

iOS架构模式——MV(X)的理解与实战

MVPDemo

由于这里主要是学习架构模式思想,所以我的命名简单粗暴,希望大家理解。

iOS架构模式——MV(X)的理解与实战

界面1

界面也很简单,就是通过点击按钮修改两个label显示的内容

Model很简单,就是一个数据结构,但在实际应用中,你可以将网络请求等一些数据处理放在这里

@interface Model : NSObject @property (nonatomic, strong) NSString *first; @property (nonatomic, strong) NSString *second; @end

要让Presenter和View通信,所以我们定义一个协议,以实现Presenter向View发送命令

@protocol MyProtocol  - (void)setFirst:(NSString *)first;
- (void)setSecond:(NSString *)second; @end

view/VIewController,实现该协议

.h @interface ViewController : UIViewController @property (nonatomic, strong) UILabel *firstLabel; @property (nonatomic, strong) UILabel *secondLabel; @property (nonatomic, strong) UIButton *tapButton; @end .m主要代码
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.firstLabel];
    [self.view addSubview:self.secondLabel];
    [self.view addSubview:self.tapButton]; self.presenter = [Presenter new];
    [self.presenter attachView:self];
}

- (void)buttonClicked{
    [self.presenter reloadView];
}

- (void)setFirst:(NSString *)first{ self.firstLabel.text = first;
}

- (void)setSecond:(NSString *)second{ self.secondLabel.text = second;
}

Presenter

.h @interface Presenter : NSObject - (void)attachView:(id )attachView;
- (void)reloadView; @end .m @interface Presenter() @property (nonatomic, weak) id  view; @property (nonatomic, strong) Model *model; @end @implementation Presenter - (instancetype)init
{ self = [super init]; if (self) { self.model = [Model new]; self.model.first = @"first"; self.model.second = @"second";
    } return self;
}

- (void)attachView:(id)attachView{ self.view = attachView;
}

- (void)reloadView{ //可以在这里做一些数据处理 [self.view setFirst:self.model.first];
    [self.view setSecond:self.model.second];
} @end

这里只是一个简单的Demo,其实思想很简单,就是讲业务逻辑交给Presenter,而Presenter以命令的形式来控制View。

完整Demo可以 看这里

一些说明:

MVP架构拥有三个真正独立的分层,所以在组装的时候会有一些问题,而MVP也成了第一个披露这种问题的架构,因为我们不想让View知道Model的信息,所以在当前的Controller去组装是不正确的,我们应该在另外的地方完成组装。比如我们可以创建一个应用层的Router服务,让它来负责组装和View-to-View的转场。这个问题下很多模式中都存在。

下面总结一下MVP的各方面表现:

  • 划分——我们把大部分职责都分配到了Presenter和Model里面,而View基本不需要做什么

  • 可测性——我们可以通过View来测试大部分业务逻辑

  • 易用——代码量差不多是MVC架构的两倍,但是MVP的思路还是蛮清晰的

另外,MVP还有一个变体,它的不同主要就是添加了数据绑定。这个版本的MVP的View和Model直接绑定,而Presenter仍然继续处理View上的用户操作,控制View的显示变化。这种架构和传统的MVC类似,所以我们基本可以舍弃。

MVVM

MVVM可以说是MV(X)系列中最新兴起的也是最出色的一种架构,而它也广受我们iOS程序员喜爱。

iOS架构模式——MV(X)的理解与实战

MVVM

MVVM和MVP很像:

  • 把ViewController看成View

  • View和Model之间没有紧耦合

另外它还让VIew和ViewModel做了数据绑定。ViewModel可以调用对Model做更改,也可以再Model更新的时候对自身进行调整,然后通过View和ViewModel之间的绑定,对View进行相应的更新。

关于绑定

在iOS平台上面有KVO和通知,但是用起来总是觉得不太方便,所以有一些三方库供我们选择:

  • 基于KVO的绑定库,如 RZDataBinding 或者  SwiftBond

  • 使用全量级的 函数式响应编程 框架,比如 ReactiveCocoa RxSwift 或者 PromiseKit

实际上,我们在提到MVVM的时候就很容易想到ReactiveCocoa,它也是我们在iOS中使用MVVM的最好工具。但是相对来说它的学习成本和维护成本 也是比较高的,而且一旦你应用不当,很可能造成灾难性的问题。

下面我暂时不用RAC来简单展示一下MVVM:

iOS架构模式——MV(X)的理解与实战

MVVM

界面很简单,就是点击一个button修改label里面的数据

iOS架构模式——MV(X)的理解与实战

界面

Model

@interface MVVMModel : NSObject @property (nonatomic, copy) NSString *text; @end @implementation MVVMModel - (NSString *)text{
    _text = [NSString stringWithFormat:@"newText%d",rand()]; return _text;
}

ViewModel

@interface MVVMViewModel : NSObject - (void)changeText; @end @interface MVVMViewModel() @property (nonatomic, strong) NSString *text; @property (nonatomic, strong) MVVMModel *model; @end @implementation MVVMViewModel - (instancetype)init
{ self = [super init]; if (self) { self.model = [MVVMModel new];
    } return self;
}

- (void)changeText{ self.text = self.model.text;;
}

Controller

@interface MVVMViewController () @property (weak, nonatomic) IBOutlet UILabel *textLabel; @property (nonatomic, strong) MVVMViewModel *viewModel; @end @implementation MVVMViewController - (void)viewDidLoad {
    [super viewDidLoad]; self.viewModel = [[MVVMViewModel alloc]init];
    [self.viewModel addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
}
- (IBAction)buttonClicked:(UIButton *)sender {
    [self.viewModel changeText];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ self.textLabel.text = change[@"new"];
}

MVVM的核心就是View和ViewModel的一个绑定,这里我只是简单的通过KVO实现,看起来并不是那么优雅,想要深度使用的话我觉得还是有必要学习一下RAC的,需要完整的Demo请 看这里 。

下面我们再来对MVVM的各方面表现做一个评价:

  • 划分——MVVM 框架里面的 View 比 MVP 里面负责的事情要更多一些。因为前者是通过 ViewModel 的数据绑定来更新自身状态的,而后者只是把所有的事件统统交给 Presenter 去处理就完了,自己本身并不负责更新。

  • 可测性—— 因为 ViewModel 对 View 是一无所知的,这样我们对它的测试就变得很简单。View 应该也是能够被测试的,但是可能因为它对 UIKit 的依赖,你会直接略过它。

  • 易用——它比MVP会更加简洁,因为在 MVP 下你必须要把 View 的所有事件都交给 Presenter 去处理,而且需要手动的去更新 View 的状态;而在 MVVM 下,你只需要用绑定就可以解决。

综上:MVVM 真的很有魅力,因为它不仅结合了上述几种框架的优点,还不需要你为视图的更新去写额外的代码(因为在 View 上已经做了数据绑定),另外它在可测性上的表现也依然很棒。

为了简单易懂,以上的Demo都非常简洁,不知道看了这篇博客能否加深你对MV(X)的一些理解,这些理解也仅作为我个人的一些参考,有什么不对的地方希望大家指出。

原文  http://www.cocoachina.com/ios/20180416/23008.html
正文到此结束
Loading...