MVVM是一种常见的设计模式,但是在工作中,我最常用的还是MVC的设计模式,MVVM只是在别人的文章中了解过,这一次正好公司项目中MVVM的设计模式,正好拿来分析一波。另外,项目并非全部是MVVM,而是和MVC设计模式掺和在一起的。当然,这并不矛盾。好了,我们就来结合实际项目来分析下MVVM的实现方式吧。
凡事趁热打铁,过了那个热乎劲想要再重新回到那个状态,难免会付出更多的时间和精力,所以还是少做傻事为妙。
一、首先我们思考几个问题。
1、VM页面怎么监控数据的变化?
2、Controller页面数据的变化怎么让VM随着数据的变化作出相应的调整呢?
3、因为VM是多个界面公用的,那么VM怎么判断不同页面的请求呢?
那就让我们来根据这几个疑问来分析吧。
二、VM页面怎么监控数据的变化?
用到的技术就是KVO。
1、首先我们在CourseVM.h类中,添加一个记录当前页面的属性。
@property (assign, nonatomic) int curPageNo;
KVO使用这个参数对数据进行监控,一旦刷新完毕,coursLoadOver的布尔值发证改变,触发KVO监控,然后调用代理,最终加载数据完毕。 也就是说,UI界面只管进行UI的处理,VM页面实时通过UI界面的值的改变监控数据的变化,然后再重新返回给UI界面,进行数据的刷新。
2、设置监控
1、设置监控参数的路径
static NSString *const kStrPageNo = @"curPageNo";
2、对参数路径进行监控
[self addObserver:self forKeyPath:kStrPageNo options:NSKeyValueObservingOptionNew context:nil];
3、当监控的参数发生改变的时候,触发监听机制,然后触发代理,在Controller页面的代理中对数据进行处理。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:kStrPageNo]) {
if (self.curPageNo == 1) {
self.totalNum = 1;
}
[self queryCompleteCourseList];
}else{
if ([_delegate respondsToSelector:@selector(observeValueForKeyPath:object:)]) {
[_delegate observeValueForKeyPath:keyPath object:object];
}
}
}
//注意:- (void)observeValueForKeyPath:(NSString *)keyPath object:(id)object;这个方法是我们写的一个代理,不是KVO的方法,别搞混了。
4、Controller中的代理设置,根据不同的监控,我们调用不同的方法。
- (void)observeValueForKeyPath:(NSString *)keyPath object:(id)object{
if ([keyPath isEqualToString:kStrClassLoadOver]) {
[self.tableView2.mj_header endRefreshing];
[self.tableView2.mj_footer endRefreshing];
self.dataList = self.classVM1.dataList;
for (ClassModel *class in self.dataList) {
if (class.lessonId.intValue == 0) {
self.trialClass = class;
break;
}
}
if (self.currentTable == self.tableView2) {
[self showNoData];
[self.currentTable reloadData];
}
}
else if([keyPath isEqualToString:kStrOpenCurseLoadOver]){
[self.tableView3.mj_header endRefreshing];
[self.tableView3.mj_footer endRefreshing];
self.openCourseList = self.openCourseVM.dataList;
if (self.currentTable == self.tableView3) {
[self showNoData];
[self.currentTable reloadData];
}
}
else if([keyPath isEqualToString:kStrCourseLoadOver]){
[self.tableView1.mj_header endRefreshing];
[self.tableView1.mj_footer endRefreshing];
self.openCourseList = self.openCourseVM.dataList;
if (self.currentTable == self.tableView1) {
[self showNoData];
[self.currentTable reloadData];
}
}
}
5、我们在Controller中设置初始值。
self.courseVM.curPageNo = 1;
这样就会有第一次的刷新,当我们再次上拉刷心的时候。每次
self.tableView2.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
self.classVM1.curPageNo = 1;
}];
6、在controller刷新的时候,增加监控参数的值,使之发生改变。这样,就会触发VM的KVO监控,然后就调用代理触发Controller中的代理,刷新数据。
三、再次思考(19.4.16)
1、监控、网络请求、传值的思想
VM中设置监控的参数,VC中给初始值,然后触发监控,调用网络请求,请求完毕之后,重新赋值数据的Array,用于VC的刷新重载数据,另外一个监控参数bool发生改变,调用代理,将数据传递出去。 [self.currentTable reloadData];重新刷新tableView.
这样的好处是,在VC页面完全没有网络的请求数据的处理,只是专注于界面数据的加载。
2、网络监听的注意点:
多次下拉刷新,每次都要删除一下bool的监听,然后再重新设置监听,这么做的原因就是每次监听相当于重新监听,这样的话就避免了每次的bool值都是一样的到时监听失效,无法调用代理的问题哦。完美
- (void)queryCompleteCourseListWithPageIndex:(int)pageIndex
pageSize:(int)pageSize
sortCol:(NSString *)sortCol
sortDir:(NSString *)sortDir
{
START_LOADING
if (pageIndex == 1) { // 下拉刷新
[self.dataList removeAllObjects];
}
if (self.dataList.count >= self.totalNum) {
self.coursLoadOver = YES;
STOP_LOADING;
return;
}
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
SetObjForDic(dic, @(pageIndex), @"pageIndex");
SetObjForDic(dic, @(pageSize), @"pageSize");
SetObjForDic(dic, @2, @"lessonStatus");
SetObjForDic(dic, @0, @"studentStatus");
SetObjForDic(dic, [UserManager shareInstance].studentInfo.id, @"studentId")
[InterfaceManager startRequest:API_LESSON_queryCourseListByStudentId
describe:@"query completed courses"
body:dic
returnClass:[ClassModel class]
completion:^(BOOL isSucceed, NSString *message, ResultModel *result)
{
STOP_LOADING;
if (isSucceed) {
NSArray * list = result.dataList;
[self.dataList addObjectsFromArray:list];
self.totalNum = result.pageInfo.totalNum;
self.totalPages = result.pageInfo.totalPages;
}
else{
//先移除监听,然后更改之后再次添加
[self removeObserver:self forKeyPath:kStrPageNo];
if (self.curPageNo > 1) {
self.curPageNo -- ;
}
//添加监听这里监听的数值发生改变,所以还会再次触发KVO监听。如果不移除,监控的参数每次都是相同的,
//会导致KVO失效,所以每次移除然后再重新添加,数据就会是全新的。
[self addObserver:self forKeyPath:kStrPageNo options:NSKeyValueObservingOptionNew context:nil];
[XHToast showCenterWithText:message];
}
self.coursLoadOver = YES;
}];
}
四、再次补充
自己在重写的时候发现一个问题,那就是设置了KVO监听之后,在第一次加载的时候,用于传递数据的block和delegate都失效了,百思不得其解。然后经过请教邓晓文同学之后,发现原来是我的KVO监控的位置太提前了。
...
注意,只是在第一次的时候block和代理无法传递至,如果再次改变curPageNo的值,此时的block和代理都能将数据传递出来,思考下到底是什么原因呢?
...
self.purVM = [[PurchaseVM alloc]init];
//这里的监听提前了,导致代理和block都还没有设置,所以根本就不会触发事件。
self.purVM.curPageNo = @"update";
self.purVM.delegate = self;
__weak typeof(self) weakSelf = self;
self.purVM.PurchaseBlock = ^(NSMutableArray * _Nonnull arr) {
weakSelf.dataArr = arr;
NSLog(@"~~~~~~~~~~~Block获取到了数据:%@",weakSelf.dataArr);
};
...
究其原因就是:监听提前了,导致代理和block都还没有来得及设置,所以第一次初始化的时候我们可以监听到变量的改变,但是代理和block根本就来不及触发,导致数据无法传递出来。我们要做的就是在设置代理和block之后再添加监听才是正确的。like this:
...
self.purVM = [[PurchaseVM alloc]init];
self.purVM.delegate = self;
__weak typeof(self) weakSelf = self;
self.purVM.PurchaseBlock = ^(NSMutableArray * _Nonnull arr) {
weakSelf.dataArr = arr;
NSLog(@"~~~~~~~~~~~Block获取到了数据:%@",weakSelf.dataArr);
};
self.purVM.curPageNo = @"update";
代理方法:
//从代理中获取数据,然后刷新表即可
- (void)purchaseDelegate:(NSMutableArray *)datalist{
self.dataArr = datalist;
NSLog(@"~~~~~~~~~~~代理获取到了数据:%@",self.dataArr);
}