iOS开发之利用MVVM框架来优化项目结构。对Controller瘦身以及MVC向MVVM框架的迁移。
2016-05-30 15:25
681 查看
MVC开发模式 :
1. 苹果官方一直推荐我们开发者使用MVC的开发模式,所以我们大部分人之前的项目都是用MVC来开发APP,这样开发,肯定会发现一个超级大的弊端,viewcontroller里边有大量的业务逻辑与视图操作逻辑,随着项目的不断的迭代,会充斥着大量的问题,我们的单元测试也好, 我们的逻辑设计,以及代码的整洁性,代码的层级性都会出现很多的问题,为此我觉得为Controller瘦身已经是非常必要的。 也有一部分开发者着不同意这种思想,但是我们也不必固守成规,虽然MVVM想必与MVC可能从代码上来讲并没有减少,反而可能增加。
但是相对应的,也会带来很多好处,下面就是MVVM 使用后的一些优点。
1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
下面通过代码来进行来进行说明
1.首先项目的层级关系图。
1.model文件夹: 这个和mvc下的model没有什么区别。 一般为瘦model.
2. view文件夹: 视图的载体, 和mvc下的view也没有什么区别, 由controller进行协调展示。
3. Controller文件夹 : 只包含一些视图的操作,不包含任何的业务处理。这里要达到瘦身的效果,经常会吧tableview 的protocal给单独提出来,本文章将的也会把这些类给提出来,后面详细说明
4.ViewModel: 把以前充斥在Controller中的一些业务处理放置在此地, 包含:数据请求,数据的包装。把封装好的数据直接传递给Controller直接进行显示。 网络请求的起飞与着陆点,这个看项目的网络层架构设计。 不能一视同仁
5.TableViewProtocol文件夹: 这个就是把Tableview的数据源与delegate给提取出来,这样做的好处就是可以最大化的重用对应的代理方法,易于管理, 并且可以让controller中及其的简洁。
6.3rdLibs文件夹: 存放了一个第三方的上下拉加载控件。
2.完整的项目代码结构目录:
下面让我们一级一级的进行代码的解剖:
1.Controller代码解剖:
TableViewController.h
TableViewController.m
#import "TableViewController.h"
#import "YiRefreshHeader.h"
#import "YiRefreshFooter.h"
#import "TableViewModel.h"
#import "TableViewDataSource.h"
#import "TableViewDelegate.h"
@interface TableViewController ()
{
YiRefreshHeader *refreshHeader;
YiRefreshFooter *refreshFooter;
NSMutableArray *totalSource;
TableViewModel *tableViewModel;
UITableView *tableView;
TableViewDataSource *tableViewDataSource;
TableViewDelegate *tableViewDelegate;
}
@end
@implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (iOS7) {
self.edgesForExtendedLayout = UIRectEdgeBottom | UIRectEdgeLeft | UIRectEdgeRight;
}
self.title=@"MVVMDemo With TableView";
self.view.backgroundColor=[UIColor whiteColor];
tableView=[[UITableView alloc] initWithFrame:CGRectMake(0, 0, WScreen, HScreen-64) style:UITableViewStylePlain];
[self.view addSubview:tableView];
tableViewDataSource = [[TableViewDataSource alloc] init]; //对tableview 数据源的封装的类
tableViewDelegate = [[TableViewDelegate alloc] init]; //对tableview 代理 封装的类,可以方便复用
tableView.dataSource=tableViewDataSource; //把当前的tableview的数据源指向我们的公用datasource类。
tableView.delegate=tableViewDelegate; //把当前的tableview的代理指向我们的公用的代理类。
tableViewModel=[[TableViewModel alloc] init]; //本章的重头戏,把业务处理提出的类
totalSource=0;
// YiRefreshHeader 头部刷新按钮的使用
refreshHeader=[[YiRefreshHeader alloc] init];
refreshHeader.scrollView=tableView;
[refreshHeader header];
__weak typeof(self) weakSelf = self;
refreshHeader.beginRefreshingBlock=^(){
__strong typeof(self) strongSelf = weakSelf;
[strongSelf headerRefreshAction]; //下拉刷新
};
// 是否在进入该界面的时候就开始进入刷新状态
[refreshHeader beginRefreshing];
// YiRefreshFooter 底部刷新按钮的使用
refreshFooter=[[YiRefreshFooter alloc] init];
refreshFooter.scrollView=tableView;
[refreshFooter footer];
refreshFooter.beginRefreshingBlock=^(){
__strong typeof(self) strongSelf = weakSelf;
[strongSelf footerRefreshAction]; //上拉刷新
};
}
// 这里是具体的下拉刷新方法,我们可以发现,这里是调用了viewModel中的方法,并把处理的结果通过block方式返回回来。
- (void)headerRefreshAction
{
[tableViewModel headerRefreshRequestWithCallback:^(NSArray *array){
totalSource=(NSMutableArray *)array;
tableViewDataSource.array=totalSource;
tableViewDelegate.array=totalSource;
[refreshHeader endRefreshing];
[tableView reloadData];
}];
}
// 这里是具体的上拉刷新方法,我们可以发现,这里是调用了viewModel中的方法,并把处理的结果通过block方式返回回来。
- (void)footerRefreshAction
{
[tableViewModel footerRefreshRequestWithCallback:^(NSArray *array){
[totalSource addObjectsFromArray:array] ;
tableViewDataSource.array=totalSource;
tableViewDelegate.array=totalSource;
[refreshFooter endRefreshing];
[tableView reloadData];
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
TableViewModel.h
TableViewModel.m , 这里应该非常的通俗易懂,就是把以前ViewController中的上下拉刷新方法放在此地,并且通过block把处理过后的结果返回给控制器。
TableViewDataSource.h
TableViewDataSource.m 把tableview的 数据源相关的代理方法全部拧出来,以后可以进行更高的复用
TableViewDelegate.h
TableViewDelegate.m
这里我就把经常使用的tableview cell的点击代理给写了出来,大家可以随意进行添加其他的代理。
其中。 model, 已经自定义的cell的具体代码我在这儿就不一一展示,和我们以前MVC的写法一模一样。
项目代码下载地址: 点击打开链接
总结:
1. 利用ViewModel把业务处理操作,全部从Controller中提出,这样在Controller中就不会充斥着大量的网络代理回调
2. 利用封装tableview的数据源 与 代理 类 ,来优化大量tableview的 协议 的冗余代码。减少代码量,方便后期维护与扩展。
后续:
既然我们把业务操作提取出来了。那如何展示出来,怎么把对应的数据传递到view层等等,这时候就诞生了ReactiveCocoa, 虽然前期有自学过,但一直没有理解到其精髓,只是会使用一些基本的用法,运用到项目中间来,我怕会有一些意想不到的坑, 所以不敢拿公司的利益来冒险,以后有时间。 自己写写demo来运用下就好。 后期再出对应的博客详细讲解它
1. 苹果官方一直推荐我们开发者使用MVC的开发模式,所以我们大部分人之前的项目都是用MVC来开发APP,这样开发,肯定会发现一个超级大的弊端,viewcontroller里边有大量的业务逻辑与视图操作逻辑,随着项目的不断的迭代,会充斥着大量的问题,我们的单元测试也好, 我们的逻辑设计,以及代码的整洁性,代码的层级性都会出现很多的问题,为此我觉得为Controller瘦身已经是非常必要的。 也有一部分开发者着不同意这种思想,但是我们也不必固守成规,虽然MVVM想必与MVC可能从代码上来讲并没有减少,反而可能增加。
但是相对应的,也会带来很多好处,下面就是MVVM 使用后的一些优点。
MVVM的优点如下:
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有以下四大优点1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
下面通过代码来进行来进行说明
1.首先项目的层级关系图。
1.model文件夹: 这个和mvc下的model没有什么区别。 一般为瘦model.
2. view文件夹: 视图的载体, 和mvc下的view也没有什么区别, 由controller进行协调展示。
3. Controller文件夹 : 只包含一些视图的操作,不包含任何的业务处理。这里要达到瘦身的效果,经常会吧tableview 的protocal给单独提出来,本文章将的也会把这些类给提出来,后面详细说明
4.ViewModel: 把以前充斥在Controller中的一些业务处理放置在此地, 包含:数据请求,数据的包装。把封装好的数据直接传递给Controller直接进行显示。 网络请求的起飞与着陆点,这个看项目的网络层架构设计。 不能一视同仁
5.TableViewProtocol文件夹: 这个就是把Tableview的数据源与delegate给提取出来,这样做的好处就是可以最大化的重用对应的代理方法,易于管理, 并且可以让controller中及其的简洁。
6.3rdLibs文件夹: 存放了一个第三方的上下拉加载控件。
2.完整的项目代码结构目录:
下面让我们一级一级的进行代码的解剖:
1.Controller代码解剖:
TableViewController.h
#import <UIKit/UIKit.h> @interface TableViewController : UIViewController @end
TableViewController.m
#import "TableViewController.h"
#import "YiRefreshHeader.h"
#import "YiRefreshFooter.h"
#import "TableViewModel.h"
#import "TableViewDataSource.h"
#import "TableViewDelegate.h"
@interface TableViewController ()
{
YiRefreshHeader *refreshHeader;
YiRefreshFooter *refreshFooter;
NSMutableArray *totalSource;
TableViewModel *tableViewModel;
UITableView *tableView;
TableViewDataSource *tableViewDataSource;
TableViewDelegate *tableViewDelegate;
}
@end
@implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (iOS7) {
self.edgesForExtendedLayout = UIRectEdgeBottom | UIRectEdgeLeft | UIRectEdgeRight;
}
self.title=@"MVVMDemo With TableView";
self.view.backgroundColor=[UIColor whiteColor];
tableView=[[UITableView alloc] initWithFrame:CGRectMake(0, 0, WScreen, HScreen-64) style:UITableViewStylePlain];
[self.view addSubview:tableView];
tableViewDataSource = [[TableViewDataSource alloc] init]; //对tableview 数据源的封装的类
tableViewDelegate = [[TableViewDelegate alloc] init]; //对tableview 代理 封装的类,可以方便复用
tableView.dataSource=tableViewDataSource; //把当前的tableview的数据源指向我们的公用datasource类。
tableView.delegate=tableViewDelegate; //把当前的tableview的代理指向我们的公用的代理类。
tableViewModel=[[TableViewModel alloc] init]; //本章的重头戏,把业务处理提出的类
totalSource=0;
// YiRefreshHeader 头部刷新按钮的使用
refreshHeader=[[YiRefreshHeader alloc] init];
refreshHeader.scrollView=tableView;
[refreshHeader header];
__weak typeof(self) weakSelf = self;
refreshHeader.beginRefreshingBlock=^(){
__strong typeof(self) strongSelf = weakSelf;
[strongSelf headerRefreshAction]; //下拉刷新
};
// 是否在进入该界面的时候就开始进入刷新状态
[refreshHeader beginRefreshing];
// YiRefreshFooter 底部刷新按钮的使用
refreshFooter=[[YiRefreshFooter alloc] init];
refreshFooter.scrollView=tableView;
[refreshFooter footer];
refreshFooter.beginRefreshingBlock=^(){
__strong typeof(self) strongSelf = weakSelf;
[strongSelf footerRefreshAction]; //上拉刷新
};
}
// 这里是具体的下拉刷新方法,我们可以发现,这里是调用了viewModel中的方法,并把处理的结果通过block方式返回回来。
- (void)headerRefreshAction
{
[tableViewModel headerRefreshRequestWithCallback:^(NSArray *array){
totalSource=(NSMutableArray *)array;
tableViewDataSource.array=totalSource;
tableViewDelegate.array=totalSource;
[refreshHeader endRefreshing];
[tableView reloadData];
}];
}
// 这里是具体的上拉刷新方法,我们可以发现,这里是调用了viewModel中的方法,并把处理的结果通过block方式返回回来。
- (void)footerRefreshAction
{
[tableViewModel footerRefreshRequestWithCallback:^(NSArray *array){
[totalSource addObjectsFromArray:array] ;
tableViewDataSource.array=totalSource;
tableViewDelegate.array=totalSource;
[refreshFooter endRefreshing];
[tableView reloadData];
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
TableViewModel.h
#import <Foundation/Foundation.h> typedef void (^callback) (NSArray *array); @interface TableViewModel : NSObject //tableView头部刷新的网络请求 - (void)headerRefreshRequestWithCallback:(callback)callback; //tableView底部刷新的网络请求 - (void)footerRefreshRequestWithCallback:(callback)callback; @end
TableViewModel.m , 这里应该非常的通俗易懂,就是把以前ViewController中的上下拉刷新方法放在此地,并且通过block把处理过后的结果返回给控制器。
#import "TableViewModel.h" #import "CustomModel.h" @interface TableViewModel () @end @implementation TableViewModel - (instancetype)init { self = [super init]; if (self) { } return self; } - (void)headerRefreshRequestWithCallback:(callback)callback { // 后台执行: dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); dispatch_async(dispatch_get_main_queue(), ^{ // 主线程刷新视图 NSMutableArray *arr=[NSMutableArray array]; for (int i=0; i<16; i++) { int x = arc4random() % 100; NSString *string=[NSString stringWithFormat:@"下拉刷新-测试数据%d",x]; CustomModel *model=[[CustomModel alloc] init]; model.title=string; [arr addObject:model]; } callback(arr); }); }); } - (void )footerRefreshRequestWithCallback:(callback)callback { // 后台执行: dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); dispatch_async(dispatch_get_main_queue(), ^{ // 主线程刷新视图 NSMutableArray *arr=[NSMutableArray array]; for (int i=0; i<16; i++) { int x = arc4random() % 100; NSString *string=[NSString stringWithFormat:@"上拉加载 -测试数据%d",x]; CustomModel *model=[[CustomModel alloc] init]; model.title=string; [arr addObject:model]; } callback(arr); }); }); } @end
TableViewDataSource.h
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface TableViewDataSource : NSObject<UITableViewDataSource> @property (nonatomic,strong) NSArray *array; @end
TableViewDataSource.m 把tableview的 数据源相关的代理方法全部拧出来,以后可以进行更高的复用
#import "TableViewDataSource.h" #import "CustomTableViewCell.h" @implementation TableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return _array.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (cell == nil) { cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; } cell.titleLabel.text=((CustomModel *)[_array objectAtIndex:indexPath.row]).title; return cell; } @end
TableViewDelegate.h
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface TableViewDelegate : NSObject<UITableViewDelegate> @property (nonatomic,strong) NSArray *array; @end
TableViewDelegate.m
这里我就把经常使用的tableview cell的点击代理给写了出来,大家可以随意进行添加其他的代理。
#import "TableViewDelegate.h" @implementation TableViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (_array.count>0) { UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"" message:((CustomModel *)[_array objectAtIndex:indexPath.row]).title delegate:nil cancelButtonTitle:@"sure" otherButtonTitles:nil, nil]; [alert show]; } } @end
其中。 model, 已经自定义的cell的具体代码我在这儿就不一一展示,和我们以前MVC的写法一模一样。
项目代码下载地址: 点击打开链接
总结:
1. 利用ViewModel把业务处理操作,全部从Controller中提出,这样在Controller中就不会充斥着大量的网络代理回调
2. 利用封装tableview的数据源 与 代理 类 ,来优化大量tableview的 协议 的冗余代码。减少代码量,方便后期维护与扩展。
后续:
既然我们把业务操作提取出来了。那如何展示出来,怎么把对应的数据传递到view层等等,这时候就诞生了ReactiveCocoa, 虽然前期有自学过,但一直没有理解到其精髓,只是会使用一些基本的用法,运用到项目中间来,我怕会有一些意想不到的坑, 所以不敢拿公司的利益来冒险,以后有时间。 自己写写demo来运用下就好。 后期再出对应的博客详细讲解它
相关文章推荐
- WPF MVVM示例讲解
- ASP.NET小结之MVC, MVP, MVVM比较以及区别(一)
- ReactiveCocoa代码实践之-更多思考
- MVVM模式中ViewModel和View、Model有什么区别?
- ReactiveCocoa代码实践之-RAC网络请求重构
- ReactiveCocoa代码实践之-UI组件的RAC信号操作
- ASP.NET小结之MVC, MVP, MVVM比较以及区别(二)
- JavaScript数据绑定实现一个简单的 MVVM 库
- 浅谈 MVC、MVP 和 MVVM 架构模式
- 翻译-ExtJs5 Mvvm
- JavaScript数据绑定实现一个简单的 MVVM 库
- 对MVC、MVP、MVVM的理解
- Silverlight中使用MVVM(1)--基础
- AngularJS中ng-class的用法
- 刘铁猛-深入浅出WPF-系列资源汇总
- Mvvm Light Toolkit for wpf/silverlight系列之Command和Events
- IOC Containers and MVVM
- ReactiveCocoa的导入
- WPF MVVM模式实践
- 关于MVVM设计模式