iOS开发中 MVVM 设计模式的探究

前言

一直在作一线的业务开发工做,天天接触业务线,时间久了就开始思考如何能优化架构、提升维护效率,因而就接触了MVVMhtml

MVVM的出现主要是为了解决在开发过程当中Controller愈来愈庞大的问题,变得难以维护,因此MVVM把数据加工的任务从Controller中解放了出来,使得Controller只须要专一于具体业务的工做,ViewModel则去负责数据加工并经过各类通知机制让View响应ViewModel的改变。react

还有一个让人很容易忽略的问题,大部分国内外资料阐述MVVM的时候都是这样排布的:Model - View - ViewModel ,
客观上形成了MVVM不须要Controller的错觉,可事实上是这样的么?
复制代码

分析使用逻辑

ViewModel部分

我最开始据说MVVM的时候是一脸懵逼的,由于我在这几个子母中间没有看到Controller的影子,我对于Controller的使用逻辑一无所知。可是直觉告诉我,Controller依然仍是不可获取的一部分,至少操做View的时候仍是会须要。ViewModel这个单词自己也让我产生了困扰,这个术语自己可能致使混乱,由于它是咱们已经知道的两个术语的混搭,不知道这个结构更偏向于ViewModel哪一边。ios

当我查阅资料的时候,网上大部分的MVVM的表述都是View <-> ViewModel <-> Model,都是在说ViewModel支持数据的双向绑定,表述模糊,我仍是不能彻底明白ViewModel的真正用处和存在乎义。git

好比像下面的说法:github

它将model信息转变为view须要的信息,同时还将命令从view传递到model。View与Model经过ViewModel实现双向绑定。数据库

个人着眼点放在了上面的第一句话上,即“它将model信息转变为view信息,同时还将命令从view传递到model”。当我进一步去理解ViewModel的做用的时候,我发现ViewModel更应该被称呼做为“View与Model中间的观察协调器”,它主要做用是拿到原始的数据,根据具体业务逻辑须要进行处理,以后将处理好的东西塞到View中去,其职责之一是静态模型,表示View显示自身所需的数据,这使View具备更清晰定义的任务,即呈现视图模型提供的数据,总结为一句话就是与View直接对应的Model,逻辑上是属于Model层设计模式

Controller部分

那么Controller的做用呢?数组

MVVM实际上是是基于胖Model的架构思路创建的,而后在胖Model中拆出两部分:Model和ViewModel。 即MVC -> 胖Model -> Model+ ViewModel这个演化过程。网络

和上文分析一致,ViewModel本质上算是Model层(做为与View显示本身所直接对应的Model),因此View并不适合直接持有ViewModel,反之则能够。由于ViewModel有可能并非必定只服务于特定的一个View,使用更加松散的绑定关系可以下降ViewModel和View之间的耦合度。如今能够说,Controller惟一关注的是使用来自ViewModel的数据配置和管理各类View,Controller的做用就是负责控制ViewViewModel的绑定和调用关系,Controller不须要了解Web服务调用,Core Data,model对象等,这些都是能够丢给ViewModel去操做。架构

如下2张图或许有更直观的感觉:

图片来源:www.sprynthesis.com/2014/12/06/…

我来解释一下图片指代的意思:

  1. MVVM中的View本质上代指的是View和Controller两部分结构。
  2. 而MVVM中的ViewModel则是抽离了Controller的一些业务出来组成了ViewModel,同时Controller的工做量减少了不少。
  3. 当Controller抽离出来ViewModel以后,MVVM中View也能够区分为View和Controller两部分以后,就成了第二张图MVCVM的结构,Controller的代销也就变小了。

因此说,归根结底MVVM更应该被形容为View <-> C <-> ViewModel <-> Model,即MVCVM的设计模式,这样就很清晰的解释了Controller的位置以及做用。

进一步梳理

经过以上,咱们能够逻辑上得出,Controller应该是持有了ViewModel和View,并对两者进行判断和匹配操做。ViewModel有可能并非必定只服务于特定的一个View,Controller当中也会有不少的View,因此,同一个Controller可能持有不少个ViewModel,以此来实现不一样的业务逻辑控制多重的View。

Controller惟一关注的是使用来自ViewModel的数据配置和管理各类View,并让ViewModel知道什么时候发生须要更改上游数据的相关用户输入。 Controller不须要知道网络请求、数据库操做以及Model等,这样就让Controller更集中的去处理具体的业务逻辑。

图片来源:www.sprynthesis.com/2014/12/06/…

  • Controller:负责View和ViewModel之间的调配和绑定关系,执行业务逻辑。
  • View:展现UI,接受Action。
  • ViewModel:网络请求,数据加工,数据持有。
  • Model:原始数据。

大体会造成如下调用关系:

图片一
图片二

代码演示

废话很少说,先上Demo:github.com/derekhuangx…

下面我会摘出部分重要代码,给你们讲解一下具体的逻辑

MarshalModel

MarshalModel.h
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger lessonCount;
复制代码

Model相对来讲就比较简单了,用来储存原始数据。


MarshalViewModel

MarshalViewModel.h

@interface MarshalViewModel : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *detailTextString;
@property (nonatomic, assign, readonly) UITableViewCellAccessoryType cellType;
- (instancetype)initWithModel:(MarshalModel *)model;
@end

MarshalViewModel.m

@interface MarshalViewModel ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *detailTextString;
@property (nonatomic, assign) UITableViewCellAccessoryType cellType;
@end

@implementation MarshalViewModel

//依赖注入
- (instancetype)initWithModel:(MarshalModel *)model ; {
	if (self = [super init]) {
		self.name = model.name;
		if (model.lessonCount > 35) {
			self.detailTextString = @"多于30门课程";
			self.cellType = UITableViewCellAccessoryDetailDisclosureButton;
		} else {
			self.detailTextString = [NSString stringWithFormat:@"课程数量:%ld", (long)model.lessonCount];
			self.cellType = UITableViewCellAccessoryNone;
		}
	}
	return self;
}
复制代码
  • ViewModel的.h文件中,只放置了初始化的代码,执行Model数据的注入,另外提供了向View暴露的接口,这部分要注意了,要加上readonly的标示符,固然也能够用GET方法代替,这样能够避免View修改ViewModel中的数据,保证数据从一条线注入,处理以后,从另外一条线取出
  • ViewModel的.m文件,能够说是对于原始数据的处理操做,这里基本上就是对于原始数据转化为View须要数据的处理。固然,你们也能够根据本身的须要增长Cache或者数据库等的操做,也能够增长网络数据获取的操做。

MarshalCell

MarshalCell.h
@interface MarshalCell : UITableViewCell
@property (nonatomic, strong) MarshalViewModel *viewModel;
@end

MarshalCell.m
- (void)setViewModel:(MarshalViewModel *)viewModel {
	_viewModel = viewModel;
    self.textLabel.text = viewModel.name;
    self.detailTextLabel.text = viewModel.detailTextString;
    self.accessoryType = viewModel.cellType;
}
复制代码

我只把.m文件中的SET方法给你们展现出来了,我以为就能够说明问题,ViewModel中提供了View须要的一切处理好的关键要素,那么View就会变得很是扁平,只须要处理点击这种的Action事件,而且传递给Controller便可,不须要关注原始数据转换的具体业务逻辑。


HomeViewController

HomeViewController.m

@interface HomeViewController () 
@property (nonatomic, strong) NSMutableArray <MarshalViewModel *>*viewModels;
@end

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

	MarshalCell *cell = [[MarshalCell alloc] initWithStyle: UITableViewCellStyleSubtitle
					       reuseIdentifier: kMarshalCellIdentifier];
	cell.viewModel = self.viewModels[indexPath.row];
	return cell;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return self.viewModels.count;
}

- (void)getData {
	[[MarshalService shareInstance]fetchDataCompletion:^(NSArray<MarshalModel *> * _Nonnull data) {
    	self.viewModels = [self viewModelsWithDatas:[data mutableCopy]];
    	dispatch_async(dispatch_get_main_queue(), ^{
    		[self.tableView reloadData];
		});
	} failure:^(NSError * _Nonnull error) {
		NSLog(@"Failed to fetch courses:%@", error);
	}];
}
复制代码

你们能够看到,我用了数组来储存的ViewModel,缘由是由于每一个Cell所对应的的数据不一致致使UI展现不一致,因此每一个Cell要有一个本身的ViewModel来填充本身UI数据。这就进一步印证了上面说的,ViewModel本质仍然是Model层

疑问点(不按期更新)

一、为何View没有.h接口的可读处理,而ViewModel中要有?

在ViewModel中数据单向注入,以后会有数据根据业务逻辑处理,以后向View提供数据接口,为了保证数据不会被污染,因此增长了`readonly`的标示符,而View中则不须要以上的逻辑。
复制代码

二、当点击某个Cell的某部分以后,如何向后传值?

1.Controller找到对应的ViewModel,ViewModel做为Mother ViewModel,替代Controller作一个脏活,去生成相对应的Child ViewModel。
2.ViewModel将Child ViewModel返回给Controller。
3.Controller利用Child ViewModel生成下一页面的Controller,执行跳转操做。
复制代码

Refrence

另外

相关文章
相关标签/搜索