前言
MMVM这个概念,相信很多人都听过,但很多人估计和我一样,没真正去理解和运用过,对它只是一知半解而已.最近因为做了新项目,有意向引入MVVM的模式,因此去进行了一些研究.研究之后,才发现涉及的点还是比较多的.
为什么要用MVVM
传统的MVC对于大项目而言,如果编码不规范,必定会造成VC臃肿
,单元测试难度大
,以及代码结构(不是风格,风格因人而异)不统一
的问题.而MVVM便解决了这些问题.
- 为VC解耦.
- 利于测试.
- 利于统一整体代码结构.
MVVM说明
各个模块的定义.
M : 表示Model,但这个Model不单纯是一个只保存属性的Model,而是将数据请求,加工,持久化
结合起来的model,也可以称作是胖model
.一般而言,model只是一个属性存储的东西,但在MVVM中,它的功能丰富了许多,正因为如此,分担了VC部分逻辑,而且更符合它的意义(提供可用数据
).
这里举一个登录的例子,这个TLoginDataModel
就代表一个Model,它的作用是发起登录请求然后将结果包装后回调.
#import
@interface TLoginDataModel : NSObject
// 提供一个登录请求方法
- (void)requestToLoginWithParam:(id)param completionBlock:(void(^)(id responseObject,NSError *error))block;
@end
V : 表示View,这个view同时也包含了vc
,因为在iOS中,VC其实可以算作其他view的容器,因为它本身提供了一个最基本的view
,view
会和ViewModel
进行绑定.
下面是一个登录界面,输入账号密码之后点击登录,viewModel
发起请求,会把Model
请求下来包装好的数据进行显示,也就表示登录成功了.
// .h文件
@class TLoginViewModel;
@interface TLoginView : UIView
// 提供一个绑定ViewModel的方法
- (void)bindViewModel:(TLoginViewModel *)viewModel;
@end
// .m文件
@interface TLoginView()
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, strong) UILabel *label1;
@property (nonatomic, strong) UILabel *label2;
@property (nonatomic, strong) UITextField *textF1;
@property (nonatomic, strong) UITextField *textF2;
@property (nonatomic, strong) TLoginViewModel *viewModel;
@end
VM : 表示ViewModel,它是View和Model之间的桥梁,让View和Model不进行直接耦合,通过ViewModel来传递数据.
具体过程是:ViewModel
绑定Model
,监听数据变化;然后View
绑定ViewModel
,当数据发生变化时,ViewModel
会通知View
发生了变化,从而更新UI显示.
// 在loginViewController内
self.model = [TLoginDataModel new];
self.viewModel = [TLoginViewModel new];
// viewModel 绑定model
[self.viewModel bindDataModel:self.model];
// view 绑定 viewModel
[self.loginView bindViewModel:self.viewModel];
// 监听网络回调
Weakify(self);
// 初始化监听,模仿的FBKVOController
self.kvoAgent = [[TObserverAgent alloc] init];
[self.kvoAgent t_addObserverForTarget:self.viewModel keyPath:@t_keypath(self.viewModel,command.result) handler:^(NSDictionary *change, id target, NSString *keyPath) {
Strongify(self);
// 请求回来之后result改变,得到回调的数据
TCommandResult *newValue = change[NSKeyValueChangeNewKey];
[self.resultLabel setText:newValue.responseObject];
}];
这里实际上是监听了viewModel
的command.result
属性,在TLoginViewModel.m
中,我们可以看到它的回调方式如下:
- (void)initItems{
Weakify(self);
// 通过TCommand进行数据传递
self.command = [[TCommand alloc] initWithRequestBlock:^(id param, CompletionHandler completionHandler) {
Strongify(self);
[self.dataModel requestToLoginWithParam:param completionBlock:^(id responseObject, NSError *error) {
SAFE_BLOCK(completionHandler,error,responseObject);
}];
}];
}
// 绑定Model
- (void)bindDataModel:(TLoginDataModel *)dataModel{
self.dataModel = dataModel;
}
这里TCommand
的作用类似于RAC里的RACCommand
,作用是执行某个操作,操作完成后,把请求的数据转发出去.我们把Model
包装的数据再次用TCommandResult
包装了一下,这样外部就可以KVOcommand.result
来更新UI了.
这样就完成了一个简单的MVVM模式
登录demo,这里也没有运用RAC
这个重型的框架,只是根据需要进行了适当工具封装便达到了同样的效果.其实MVVM重点就在于View和Model的双向绑定,如何优雅的解决它,就是RAC
的价值体现.
但本人并不是很喜欢用它,所以后续会自己实现一些比较优雅的方法来实现同样的效果.
demo地址