iOS开发之--MVC 架构模式
随着项目开发时间的增加,从刚开始那种很随意的代码风格,逐渐会改变,现在就介绍下MVC的架构模式,MVC的架构模式,从字面意思上讲,即:MVC 即 Modal View Controller(模型 视图 控制器),是 Xerox PARC 在 20 世纪 80 年代为编程语言 Smalltalk-80 发明的一种软件设计模式,至今已广泛应用于用户交互应用程序中。其用意在于将数据与视图分离开来。在 iOS 开发中 MVC 的机制被使用的淋漓尽致,充分理解 iOS 的 MVC 模式,有助于我们程序的组织合理性。
MVC的几个明显的特征和体现:
1、View上面显示什么东西,取决于Model
2、只要Model数据改了,View的显示状态会跟着改变
3、control负责初始化Model,并将Model传递给View去解析展示
-
1)Modal 模型对象:
- 模型对象封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。例如,模型对象可能是表示商品数据 list。用户在视图层中所进行的创建或修改数据的操作,通过控制器对象传达出去,最终会创建或更新模型对象。模型对象更改时(例如通过网络连接接收到新数据),它通知控制器对象,控制器对象更新相应的视图对象。
-
2)View 视图对象:
-
视图对象是应用程序中用户可以看见的对象。视图对象知道如何将自己绘制出来,可能对用户的操作作出响应。视图对象的主要目的就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象通常与模型对象分离。
-
在iOS应用程序开发中,所有的控件、窗口等都继承自 UIView,对应 MVC 中的 V。UIView 及其子类主要负责 UI 的实现,而 UIView 所产生的事件都可以采用委托的方式,交给 UIViewController 实现。
-
-
3)Controller 控制器对象:
-
在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介。控制器对象因此是同步管道程序,通过它,视图对象了解模型对象的更改,反之亦然。控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。
-
控制器对象解释在视图对象中进行的用户操作,并将新的或更改过的数据传达给模型对象。模型对象更改时,一个控制器对象会将新的模型数据传达给视图对象,以便视图对象可以显示它。
-
对于不同的 UIView,有相应的 UIViewController,对应 MVC 中的 C。例如在 iOS 上常用的 UITableView,它所对应的 Controller 就是UITableViewController。
-
4、iOS MVC示意图
-
1)Model 和 View 永远不能相互通信,只能通过 Controller 传递。
-
2)Controller 可以直接与 Model 对话(读写调用 Model),Model 通过 Notification 和 KVO 机制与 Controller 间接通信。
-
3)Controller 可以直接与 View 对话,通过 outlet,直接操作 View,outlet 直接对应到 View 中的控件,View 通过 action 向 Controller 报告事件的发生(如用户 Touch 我了)。Controller 是 View 的直接数据源(数据很可能是 Controller 从 Model 中取得并经过加工了)。Controller 是 View 的代理(delegate),以同步 View 与 Controller。
备注:有时候,我们在写代码的时候,会把view和viewcontroller混淆在一起,严格意义上来说,已经违背了mvc的原则,但是我们一时又没发现又什么不妥,如果严格遵守 MVC 的话,你会把对 cell 的设置放在 Controller 中,不向 View 传递一个 Model 对象,这样就会大大减少 Controller 的体积。Cocoa 的 MVC 被写成 Massive View Controller 是不无道理的。
-
MVC 是一个用来组织代码的权威范式,也是构建 iOS App 的标准模式。Apple 甚至是这么说的。在 MVC 下,所有的对象被归类为一个 model,一个 view,或一个 controller。Model 持有数据,View 显示与用户交互的界面,而 View Controller 调解 Model 和 View 之间的交互。然而,随着模块的迭代我们越来越发现 MVC 自身存在着很多不足。
-
1)MVC 在现实应用中的不足:
- 在 MVC 模式中 view 将用户交互通知给控制器。view 的控制器通过更新 Model 来反应状态的改变。Model(通常使用 Key-Value-Observation)通知控制器来更新他们负责的 view。大多数 iOS 应用程序的代码使用这种方式来组织。
-
2)愈发笨重的 Controller:
-
在传统的 app 中模型数据一般都很简单,不涉及到复杂的业务数据逻辑处理,客户端开发受限于它自身运行的的平台终端,这一点注定使移动端不像 PC 前端那样能够处理大量的复杂的业务场景。然而随着移动平台的各种深入,我们不得不考虑这个问题。传统的 Model 数据大多来源于网络数据,拿到网络数据后客户端要做的事情就是将数据直接按照顺序画在界面上。随着业务的越来越来的深入,我们依赖的 service 服务可能在大多时间无法第一时间满足客户端需要的数据需求,移动端愈发的要自行处理一部分逻辑计算操作。这个时间一惯的做法是在控制器中处理,最终导致了控制器成了垃圾箱,越来越不可维护。
-
控制器 Controller 是 app 的 “胶水代码”,协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的 loading、appearing、disappearing 等等,同时往往也会充满我们不愿暴露的 Model 的模型逻辑以及不愿暴露给视图的业务逻辑。这引出了第一个关于 MVC 的问题...
-
视图 view 通常是 UIKit 控件(component,这里根据习惯译为控件)或者编码定义的 UIKit 控件的集合。进入 .xib 或者 Storyboard 会发现一个 app、Button、Label 都是由这些可视化的和可交互的控件组成。View 不应该直接引用 Model,并且仅仅通过 IBAction 事件引用 controller。业务逻辑很明显不归入 view,视图本身没有任何业务。
-
厚重的 View Controller 由于大量的代码被放进 viewcontroller,导致他们变的相当臃肿。在 iOS 中有的 view controller 里绵延成千上万行代码的事并不是前所未见的。这些超重 app 的突出情况包括:厚重的 View Controller 很难维护(由于其庞大的规模);包含几十个属性,使他们的状态难以管理;遵循许多协议(protocol),导致协议的响应代码和 controller 的逻辑代码混淆在一起。
-
厚重的 view controller 很难测试,不管是手动测试或是使用单元测试,因为有太多可能的状态。将代码分解成更小的多个模块通常是件好事。
-
-
3)太过于轻量级的 Model:
- 早期的 Model 层,其实就是如果数据有几个属性,就定义几个属性,ARC 普及以后我们在 Model 层的实现文件中基本上看不到代码(无需再手动管理释放变量,Model 既没有复杂的业务处理,也没有对象的构造,基本上 .m 文件中的代码普遍是空的);同时与控制器的代码越来厚重形成强烈的反差,这一度让人不禁对现有的开发设计构思有所怀疑。
-
4)遗失的网络逻辑:
-
苹果使用的 MVC 的定义是这么说的:所有的对象都可以被归类为一个 Model,一个 view,或是一个控制器。就这些,那么把网络代码放哪里?和一个 API 通信的代码应该放在哪儿?
-
你可能试着把它放在 Model 对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的 Model 生命周期更长,事情将变的复杂。显然也不应该把网络代码放在 view 里,因此只剩下控制器了。这同样是个坏主意,因为这加剧了厚重控制器的问题。那么应该放在那里呢?显然 MVC 的 3 大组件根本没有适合放这些代码的地方。
-
-
5)较差的可测试性
-
MVC 的另一个大问题是,它不鼓励开发人员编写单元测试。由于控制器混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。大多数人选择忽略这个任务,那就是不做任何测试。
-
上文提到了控制器可以管理视图的层次结构;控制器有一个 “view” 属性,并且可以通过 IBOutlet 访问视图的任何子视图。当有很多 outlet 时这样做不易于扩展,在某种意义上,最好不要使用子视图控制器(child view controller)来帮助管理子视图。在这里有多个模糊的标准,似乎没有人能完全达成一致。貌似无论如何,view 和对应的 controller 都紧紧的耦合在一起,总之,还是会把它们当成一个组件来对待。Apple 提供的这个组件一度以来在某种程度误导了大多初学者,初学者将所有的视图全部拖到 xib 中,连接大量的 IBoutLet 输出口属性,都是一些列问题。
-
mvc的使用,下面附上一个我写的demo:
项目架构如下:
model的创建:
.h
#import <Foundation/Foundation.h> @interface MyModel : NSObject @property(nonatomic,strong)NSString *cate_name; @property(nonatomic,strong)NSString *icon; @property(nonatomic,strong)NSString *ida; //-(id)initWithDictionary:(NSDictionary *)dict; +(instancetype)myModelWithDict:(NSDictionary *)dict; -(void)setValue:(id)value forUndefinedKey:(NSString *)key; @end
.m
#import "MyModel.h" @implementation MyModel //-(id)initWithDictionary:(NSDictionary *)dict //{ // self = [super init]; // if (self) { // [self setValuesForKeysWithDictionary:dict]; // } // return self; //} +(instancetype)myModelWithDict:(NSDictionary *)dict { // return [[self alloc]initWithDictionary:dict]; MyModel *model = [[self alloc]init]; [model setValuesForKeysWithDictionary:dict]; return model; } -(void)setValue:(id)value forUndefinedKey:(NSString *)key{ if ([key isEqualToString:@"id"]) { self.ida = value; } }
注:这里面用到了字典快速赋值,很方便,有兴趣的朋友可以参考:http://www.jianshu.com/p/870eb4b4170a
view的创建,我这里直接有的tableviewcell,代码如下:
.h
#import <UIKit/UIKit.h> #import "MyModel.h" @class MyModel; @interface MyCell : UITableViewCell @property (weak, nonatomic) IBOutlet UIImageView *headerImg; @property (weak, nonatomic) IBOutlet UILabel *nickLab; @property (nonatomic, strong) MyModel *hmodels;
.m
#import "MyCell.h" #import "SDWebImageCompat.h" #import "UIImageView+WebCache.h" @class MyModel; @implementation MyCell //从model里面取出数据来更新cell上的内容 -(void)setHmodels:(MyModel *)hmodels { _hmodels = hmodels; [_headerImg sd_setImageWithURL:[NSURL URLWithString:hmodels.icon]]; _nickLab.text = hmodels.cate_name; }
cell我是直接用xib创建的,一块以快为主!
controller的创建,
.h
.m
#define main [UIScreen mainScreen].bounds.size #define hWidht main.width #define hHeight main.height #import "ViewController.h" #import "HttpTools.h" #import "MyModel.h" #import "MyCell.h" #import "SDWebImageCompat.h" #import "UIImageView+WebCache.h" @class MyModel,MyCell; @interface ViewController ()<UITableViewDelegate,UITableViewDataSource> { UITableView *_myTableView; } @property(nonatomic,strong)NSMutableArray *dataAry; @end @implementation ViewController -(NSMutableArray *)dataAry { if (!_dataAry) { _dataAry = [NSMutableArray array]; } return _dataAry; } -(void)viewWillAppear:(BOOL)animated { [HttpTools requestWithURLString:@"http://chat.baomihua6.com/?num=1007&p=1&list_row=10" parameters:nil type:HttpRequestTypePost success:^(id responseObject) { NSLog(@"----%@",responseObject); NSDictionary *dict = (NSDictionary *)responseObject; NSArray *arr = dict[@"data"]; NSLog(@"arr is %@",arr); for (NSDictionary *hdic in arr) { MyModel *model = [MyModel myModelWithDict:hdic]; [[self dataAry] addObject:model]; NSLog(@"name is %@",model.cate_name); } NSLog(@"---dataary is %@",[self dataAry] ); #pragma mark 一般的模式 // _dataAry = [NSMutableArray arrayWithArray:dict[@"data"]]; dispatch_async(dispatch_get_main_queue(),^{ [_myTableView reloadData]; }); } failure:^(NSError *error) { }]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. _myTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, hWidht, hHeight) style:UITableViewStylePlain]; _myTableView.delegate = self; _myTableView.dataSource = self; _myTableView.tableFooterView = [[UIView alloc]init]; [_myTableView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil] forCellReuseIdentifier:@"MyCell"]; [self.view addSubview:_myTableView]; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self dataAry] .count; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 80; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; if (!cell) { cell = [[MyCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"]; } #pragma mark 一般的模式 // [cell.headerImg sd_setImageWithURL:[NSURL URLWithString:[_dataAry[indexPath.row] objectForKey:@"icon"]] placeholderImage:nil]; // cell.nickLab.text = [NSString stringWithFormat:@"%@",[_dataAry[indexPath.row] objectForKey:@"cate_name"]]; cell.hmodels =[self dataAry] [indexPath.row]; return cell; }
其实严格来说,不算是mvc,因为在controller里面有创建了view,可以直接创建一个view,然后在里面创建tableview,这样的话,就把展示层和逻辑层彻底剥离了!
最终效果如下:
上面的demo,在controller里面还是有view的创建,下面这个demo的话,剥离了出来,废话不多说,代码如下:
项目架构如下:
这里只粘贴新建view的类和controller里面的代码:
view的创建:
.h
#import <UIKit/UIKit.h> #import "MyModel.h" #import "MyCell.h" @interface MyView : UIView<UITableViewDelegate,UITableViewDataSource> @property(nonatomic,strong)UITableView *myTableView; @property(nonatomic,strong)NSMutableArray *dataAry; @end
.m
#define main [UIScreen mainScreen].bounds.size #define hWidht main.width #define hHeight main.height #import "MyView.h" @implementation MyView -(NSMutableArray *)dataAry { if (!_dataAry) { _dataAry = [NSMutableArray array]; } return _dataAry; } -(instancetype)init { self = [super init]; if (self) { _myTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, hWidht, hHeight) style:UITableViewStylePlain]; _myTableView.delegate = self; _myTableView.dataSource = self; _myTableView.tableFooterView = [[UIView alloc]init]; [_myTableView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil] forCellReuseIdentifier:@"MyCell"]; [self addSubview:_myTableView]; } return self; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self dataAry] .count; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return 80; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; if (!cell) { cell = [[MyCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"]; } #pragma mark 一般的模式 // [cell.headerImg sd_setImageWithURL:[NSURL URLWithString:[_dataAry[indexPath.row] objectForKey:@"icon"]] placeholderImage:nil]; // cell.nickLab.text = [NSString stringWithFormat:@"%@",[_dataAry[indexPath.row] objectForKey:@"cate_name"]]; cell.hmodels =[self dataAry] [indexPath.row]; return cell; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end
controller里面的代码改动如下:
.h
.m
#import "ViewController.h" #import "HttpTools.h" #import "MyModel.h" #import "MyView.h" @class MyModel,MyCell; @interface ViewController () @property(nonatomic,strong)NSMutableArray *dataAry; @property(nonatomic,strong)MyView *hviews; @end @implementation ViewController -(NSMutableArray *)dataAry { if (!_dataAry) { _dataAry = [NSMutableArray array]; } return _dataAry; } -(void)viewWillAppear:(BOOL)animated { [HttpTools requestWithURLString:@"http://chat.baomihua6.com/?num=1007&p=1&list_row=10" parameters:nil type:HttpRequestTypePost success:^(id responseObject) { NSLog(@"----%@",responseObject); NSDictionary *dict = (NSDictionary *)responseObject; NSArray *arr = dict[@"data"]; NSLog(@"arr is %@",arr); for (NSDictionary *hdic in arr) { MyModel *model = [MyModel myModelWithDict:hdic]; [[self dataAry] addObject:model]; NSLog(@"name is %@",model.cate_name); } NSLog(@"---dataary is %@",[self dataAry] ); dispatch_async(dispatch_get_main_queue(),^{ [self.hviews.myTableView reloadData]; }); } failure:^(NSError *error) { }]; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.hviews = [[MyView alloc]init]; [self.view addSubview:self.hviews]; self.hviews.frame = self.view.bounds; self.hviews.dataAry = [self dataAry]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
这样的话,就把展示层完全剥离了出来,在controller里面只负责数据直接的传递和数据的更新!
本文来自博客园,作者:稻草人11223,转载请注明原文链接:https://www.cnblogs.com/hero11223/p/6402438.html