卷首
最近新工作中用到的RAC+MVVM的开发模式,由于之前都是用MVC,从自己的菜鸡水平感觉这两种设计模式在思想上还是有些微区别的,然后自己也是看了挺多关于这两个模式异同与使用利弊的文章,但是说真的,代码这个东西光看看不出个花来,还是要写出来才能体会的更深,所以我不讲这两种模式的来龙去脉,我也讲不清 ^_^, 要是看过比较多理论上的东西,再结合一下代码理理思路还是极好滴。
目的介绍
上面已经说了,这是一个关于怎样用代码实现mvvm的记录,本来之前就想写一个极其简单的tableview的代码就行了来着,但正好在项目里面遇到一个比较弱鸡的问题,当时思路一下卡住了,就是一个基础知识,但是当时没有想通,这种问题吧,在网上搜都不知道怎么搜,在开发的时候还是比较影响开发效率的,所以回来也把那一点儿东西加上了,其实并没有几行代码,就是想给自己提个醒。
效果图如下
代码介绍
效果图就是上面的,功能比较简单,大牛们肯定看都不带看的那种,哈哈哈,我就是比价喜欢从这种简单功能上弄懂一些东西,简单的都不会,就更不指望去搞高深的了。话不多说,首先看一下主要的代码结构:
屏幕快照 2017-07-25 下午11.11.00.png
这里分了四个文件夹用来存放view/model/controller/viewModel 当然有些有些view也可以放在viewController里面,这个并没有什么严格要求。viewModel主要是用来处理数据逻辑,将model进行处理之后和view/controller进行交互,我理解的它是一个数据加工工厂,这样做的目的也就是避免在controller里面处理大量与界面业务逻辑无关的工作嘛,将数据处理专门用viewModel进行处理。。。(具体的还是参考详细的文章吧,我大概一说)分别说一下各个模块中的代码实现吧,很好理解。
Controller
控制器中就这几行代码,将自定义的mainView进行frame布局,初始化viewModel,是不是看着要比以前养眼了。
- (void)viewDidLoad { [super viewDidLoad]; self.title = @"第一页"; self.view.backgroundColor = [UIColor whiteColor]; self.viewModel = [LGJMainViewModel new]; [self configMainView]; } #pragma mark - configView - (void)configMainView { self.mainView = [[LGJMainView alloc] initWithViewModel:self.viewModel]; self.mainView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height); [self.view addSubview:self.mainView]; }
LGJMainView
就是我们看到的tableview一个自定义的view,用来“盛放”我们自定义的view,这里贴上来的代码就是将tableview单独放在这个view里面进行出来,当然这里的这些代理方法如果你想使这个view简化还是可以将他们封装出来的,我之前有写过一个对tableview代理方法优化的记录iOS实现UITableViewDataSource与Controller的分离可以参考这个进一步优化,这里面没有什么多说的,就有一点和之前不一样的,就是多了几个self.viewModel的方法,这个我们下面说
#pragma mark - tableView delegate&dataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.viewModel getSectionCount]; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { SectionModel *sectionModel = [self.viewModel getSectionModelWithSection:section]; UILabel *headerLabel = [self configHeader]; headerLabel.text = [NSString stringWithFormat:@"我是第%@个section", sectionModel.sectionName]; return headerLabel; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 50; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.viewModel getCellCountWithIndexPath:section]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *idStr = @"LGJCell"; LGJCell *cell = [tableView cellForRowAtIndexPath:indexPath]; if (!cell) { cell = [[LGJCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:idStr]; } cell.cellModel = [self.viewModel getRowModelWithIndexPath:indexPath]; return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 50; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [self.viewModel changeCellModelWithIndexPath:indexPath]; [tableView reloadData]; } #pragma mark - coustom tableViewHeader - (UILabel *)configHeader { UILabel *headerLabel = [UILabel new]; headerLabel.backgroundColor = [UIColor whiteColor]; headerLabel.font = [UIFont systemFontOfSize:14]; headerLabel.textColor = [UIColor redColor]; headerLabel.textAlignment = NSTextAlignmentCenter; return headerLabel; }
LGJViewModel
看一下.h文件中,主要就是一些外部需要调用的方法,比如在这里我们使用比较多的就是和tableview代理方法相关的很多方法比较多,如果是在MVC中,那么我们这些数据操作很有可能会写在controller里面,controller里面的内容也就不像我们刚看见的那样简洁了,还有一个就是我在.h文件中声明了一个block,其实用RAC+MVVM开发的话,RAC框架有很多自身封装好的的block也就是Signal供我们使用,也就减少了我们比较容易头疼也比较容易忽略的block的循环引用和内存泄漏,等我再熟悉熟悉RAC再专门去说它吧,这里我们就用block先这样处理,这个不是不可能的。(在这里这个block没有用到,一开始想用来处理一些东西来着,后来没有用,之所以没有删,是想说一下,在mvvm中如果我们没有用RAC框架,我们可以用block来进行一些回调操作)
.h typedef void(^UpdateCellBlock)(NSIndexPath *indexPath); @interface LGJMainViewModel : NSObject @property (nonatomic, copy) UpdateCellBlock updateCellBlock; - (void)changeCellModelWithIndexPath:(NSIndexPath *)indexPath; - (RowModel *)getRowModelWithIndexPath:(NSIndexPath *)indexPath; - (NSInteger)getCellCountWithIndexPath:(NSInteger)section; - (NSInteger)getSectionCount; - (SectionModel *)getSectionModelWithSection:(NSInteger)section;
在.m文件中我做了一个假数据,用来模拟section和cell中的数据,这个会有用的,就在下面我要说的那个坑。
.m @interface LGJMainViewModel () @property (nonatomic, strong) NSMutableArray *listArr;//盛放所有model的数组 @end @implementation LGJMainViewModel - (instancetype)init { if (self = [super init]) { [self configModelArr]; } return self; } - (void)configModelArr { self.listArr = [NSMutableArray array]; for (int i = 0; i < 10; i++) { SectionModel *model = [SectionModel new]; model.sectionName = [NSString stringWithFormat:@"%d", i]; NSMutableArray *mutArr = [NSMutableArray array]; for (int j = 0; j < 20; j++) { RowModel *rowModel = [RowModel new]; rowModel.name = [NSString stringWithFormat:@"第%d行", j]; rowModel.detail = [NSString stringWithFormat:@"我是第%d行, 多多指教", j]; [mutArr addObject:rowModel]; } model.rowModelArr = mutArr; [self.listArr addObject:model]; } }
这里就是我们在.h文件中看见的那些方法的实现了,在viewModel中对请求的数据或者本地的数据处理之后,返回给外部使用(这里说的不专业了,明白这个道理就好&—— &)
#pragma mark - get CellModel - (RowModel *)getRowModelWithIndexPath:(NSIndexPath *)indexPath { SectionModel *secModel = [self.listArr objectAtIndex:indexPath.section]; NSArray *rowArr = secModel.rowModelArr; RowModel *rModel = [rowArr objectAtIndex:indexPath.row]; return rModel; } #pragma mark - cell/section Count - (NSInteger)getSectionCount { return self.listArr.count; } - (NSInteger)getCellCountWithIndexPath:(NSInteger)section { SectionModel *secModel = [self.listArr objectAtIndex:section]; return secModel.rowModelArr.count; } #pragma mark - get SectionModel - (SectionModel *)getSectionModelWithSection:(NSInteger)section { SectionModel *sModel = [self.listArr objectAtIndex:section]; return sModel; }
这个坑就是在这儿,想实现的效果是,当我点击cell的时候,我替换这个cell对应的model数据,一开始是用的被注释掉的方法,这个稍微有些经验的都能想到这个不行,可是我就是那个掉坑的,本来想着我找到对应的section的model,在sectionModel中找到对应的RowModel然后将新model替换掉旧的。perfect。。。运行之后发现是行不通的,然后用下面在数组中遍历查找的方法进行替换解决的。关于这问题我的理解是, 数组中存放的是model的指针,我用newModel替换oldModel,替换的只是model的指针,但是数组中储存model的指针没有改变,所以数组并不会改变它保存的对应位置的指针,所以说数组中对应位置储存的还是oldModel的指针。 这个是我的理解,如果有不对麻烦告知了。
#pragma mark - change Cell Model - (void)changeCellModelWithIndexPath:(NSIndexPath *)indexPath { // RowModel *rm = [RowModel new]; // rm.name = @"新替换的name"; // rm.detail = @"新替换的detail"; // // SectionModel *sModel = [SectionModel new]; // sModel = [self.listArr objectAtIndex:indexPath.section]; // NSArray *tempCellArr = sModel.rowModelArr; // RowModel *rModel = [tempCellArr objectAtIndex:indexPath.row]; // // rModel = rm; // // if (self.updateCellBlock) { // self.updateCellBlock(indexPath); // } for (int i = 0; i < self.listArr.count; i++) { SectionModel *sModel = [SectionModel new]; if (i == indexPath.section) { sModel = [self.listArr objectAtIndex:i]; for (int j = 0; j < sModel.rowModelArr.count; j++) { RowModel *rModel = [RowModel new]; if (j == indexPath.row) { rModel = [sModel.rowModelArr objectAtIndex:j]; rModel.name = @"替换***"; rModel.detail = @"我是被替换的新cell"; } } } } }
关于model还是我们以前用的
@interface SectionModel : NSObject @property (nonatomic, copy) NSString *sectionName; @property (nonatomic, strong) NSArray *rowModelArr; @end
总结
写这篇主要是记录一个最基本的mvvm思想的实现还有一个以后绝对不能再犯的错误,遇到问题多想想,别浮躁。晚安,明天还要上班,还有bug等我拯救。。。
demo:https://github.com/irembeu/LGJ_MVVM_TestDemo
作者:劉光軍_Shine
链接:http://www.jianshu.com/p/1bf286bc2498
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。