小酌下MVVM
- 在这呢也不赘述什么是MVC,神马又是MVVM了,在百度上谷歌一下一抓一大把,在这儿就简单的提上一嘴。下面的Demo用的就是MVVM的架构模式。附上连接https://github.com/lanyuzx/iOS-Demo-
Model层是少不了的了,我们得有东西充当DTO(数据传输对象),当然,用字典也是可以的,编程么,要灵活一些。Model层是比较薄的一层。 - ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
- View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。
不扯犊子,直接进入话题
该demo,是使用开发中常用的tabView作为例子演示的,简单移动
首先我们看控制器代码如下
@interface LLMVVMViewController ()
//自定义一个tabView
@property (nonatomic,strong) LLMVVMTabView * tabView;
//记录下拉刷新的索引
@property (nonatomic,assign) NSInteger pageIndex;
@end
@implementation LLMVVMViewController
/// MARK: ---- 试图控制器
> -(void)viewDidLoad {
[super viewDidLoad];
self.pageIndex = 1;
[self.view addSubview:self.tabView];
[self setupData];
}
-(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)dealloc {
NSLog(@"%s",__func__);
NSLog(@"***********------------*************");
NSLog(@"*******************MVVM使用很多的回掉方法,一定要注意循环引用问题*********************");
NSLog(@"***********------------*************");
}
/// MARK: ---- 请求网络
-(void)setupData {
__weak typeof(self) weak = self;
[[LLMVVMViewModel shareViewModel]setupRequsetDate:self.pageIndex :^(id sucess) {
weak.tabView.modelArr = sucess;
[weak.tabView.mj_header endRefreshing];
[weak.tabView.mj_footer endRefreshing];
} :^(id error) {
}];
}
/// MARK: ---- 懒加载
-(LLMVVMTabView *)tabView {
if (_tabView == nil) {
_tabView = [[LLMVVMTabView alloc]initWithFrame:CGRectMake(0, 0, LLScreenW, LLScreenH ) style:UITableViewStylePlain];
__weak typeof(self) weak = self;
//tabViewcell的点击跳转方法
_tabView.block = ^(LLMVVMModel * model) {
[[LLMVVMViewModel shareViewModel]movieDetailWithPublicModel:model WithViewController:weak];
};
//下拉刷新方法
_tabView.blockHeader = ^(NSInteger pageNum) {
weak.pageIndex = pageNum;
[weak setupData];
};
//上拉加载的回掉方法
_tabView.blockFooter = ^(NSInteger pageNum) {
weak.pageIndex = pageNum;
[weak setupData];
};
}
return _tabView;
}
首页就是在程序启动时间,试图加载完成后进行了网络请求,并且添加了子控件,就干了这么多事,是不是感觉清晰了好多
接着我们看ViewModel代码.h如下
@interface LLMVVMViewModel : NSObject
//回调网络请求的模型数据
-(void)setupRequsetDate :(NSInteger)pageIndex :(SuccessBlock)successblock :(ErrorBlock) errorblock;
//创建该类的单例,防止重复创建
+(LLMVVMViewModel *)shareViewModel;
//跳转到电影详情页
- (void)movieDetailWithPublicModel: (LLMVVMModel *)movieModel WithViewController: (UIViewController *)superController;
@end
接着我们看ViewModel代码.m如下
@interface LLMVVMViewModel()
//创建一个数组,记录网络请求的数据
@property (nonatomic,strong) NSMutableArray * modelArr;
@end
@implementation LLMVVMViewModel
//创建一个单例,防止重复创建
+(LLMVVMViewModel *)shareViewModel{
static LLMVVMViewModel * shareViewModel ;
static dispatch_once_t predicate;
dispatch_once(&predicate,^{
shareViewModel = [[self alloc]init];
});
return shareViewModel;
}
-(void)setupRequsetDate :(NSInteger)pageIndex :(SuccessBlock)successblock :(ErrorBlock) errorblock {
//模拟异步加载数据 ,从本地去数据
__weak typeof(self) weakSelf = self;
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSString * dataPath = [[NSBundle mainBundle]pathForResource:@"MVVM" ofType:nil];
NSData * data = [NSData dataWithContentsOfFile:dataPath];
NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSArray * modelTemp = [LLMVVMModel BAMJParse:[jsonDict objectForKey:@"subjects"]];
if (pageIndex == 1) {
NSLog(@"下拉刷新");
weakSelf.modelArr = [NSMutableArray arrayWithArray:modelTemp];
}else {
[weakSelf.modelArr addObjectsFromArray:weakSelf.modelArr];
NSLog(@"上拉加载");
}
//回掉给请求者
successblock( weakSelf.modelArr);
});
}
//这里是点击cell跳转的方法,在MVC中交给控制器处理的,在MVVM中交给ViewModel处理
-(void)movieDetailWithPublicModel:(LLMVVMModel *)movieModel WithViewController:(UIViewController *)superController {
MovieViewController *movieVC = [[MovieViewController alloc] init];
LLMVVMDetailModel * model = movieModel.casts.firstObject;
movieVC.url = model.alt;
[superController.navigationController pushViewController:movieVC animated:YES];
}
-(NSMutableArray *)modelArr {
if (_modelArr == nil ) {
_modelArr = [NSMutableArray array];
}
return _modelArr;
}
在ViewModel中,我们处理了数据的解析,以及上下拉刷新的数据处理,回掉给控制器,再有控制器的子试图接收数据,刷新表格
接着我们继续看 在试图tabView中代码
@class LLMVVMModel;
//定义每个cell点击的block
typedef void(^MVVMTabViewBlock)(LLMVVMModel*);
//上下拉刷新的页码传递
typedef void(^refreshHeaderBlock)(NSInteger pageNum);
typedef void(^refreshFooterBlock)(NSInteger pageNum);
@interface LLMVVMTabView : UITableView
//模型数组
@property (nonatomic,strong) NSArray * modelArr;
@property (nonatomic,copy) MVVMTabViewBlock block;
@property (nonatomic,copy) refreshHeaderBlock blockHeader ;
@property (nonatomic,copy) refreshHeaderBlock blockFooter;
@end
@interface LLMVVMTabView()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic,assign) NSInteger pageIndex;
@end
@implementation LLMVVMTabView
//从写模型的Set方法,获取新的数据
-(void)setModelArr:(NSArray *)modelArr {
_modelArr = modelArr;
[self reloadData];
}
//构造方法
-(instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
if (self = [super initWithFrame:frame style:style]) {
self.dataSource = self;
self.delegate = self;
[self registerClass:[LLMVVMTableViewCell class] forCellReuseIdentifier:@"LLMVVMTableViewCell"];
__weak typeof(self) weak = self;
//下拉刷新
self.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
weak.pageIndex = 1;
//回掉页码数
weak.blockHeader(weak.pageIndex);
}];
//上拉刷新
self.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
weak.pageIndex++;
//回掉页码数
weak.blockFooter(weak.pageIndex);
}];
}
return self;
}
/// MARK: ---- 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.modelArr.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LLMVVMTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"LLMVVMTableViewCell"];
LLMVVMModel * model = self.modelArr[indexPath.row];
cell.model = model;
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//点击毁掉给控制器,再有控制器传递给ViewModel
self.block(self.modelArr[indexPath.row]);
}
MVVM的缺点
-
这种做法是能够提高后续操作代码的可读性的。在比较直觉的思路里面,是需要这部分转化过程的,但这部分转化过程的成本是很大的,主要成本在于:
-
数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
-
转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
-
只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
-
调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
-
同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方
网友评论