一.什么是MVC?
MVC(Model View Controller) 模型(model)-视图(view)-控制器(controller): MVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器。使用MVC是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据你可以分别用柱状图、饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新,从例子可以看出MVC就是Observer设计模式的一个特例。
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。
Model :当视图层中用于创建或修改数据的用户操作通过控制器对象进行通信,从而创建或更新模型对象。当模型对象发生更改时(例如,通过网络连接接收新数据),它会通知控制器对象,该对象会更新相应的视图对象。
View :查看对象通过应用程序的控制器对象了解模型数据的更改,并将用户启动的更改(例如,在文本字段中输入的文本 - 通过控制器对象 - 传递到应用程序的模型对象。
Controller :控制器对象解释在视图对象中进行的用户操作,并将新的或更改的数据传递给模型层。当模型对象发生更改时,控制器对象会将新模型数据传递给视图对象,以便它们可以显示它。
MVC模式能够完成各司其职的任务模式,由于降低了各个环节的耦合性,大大优化Controller的代码量,当程序调试时,如果某一个功能没有按照既定的模式工作,可以很方便的定位到到底是Controller还是View还是Model出了问题,而且还利于程序的可复用性,建议在程序中能多多使用这个模式。
苹果官方对MVC的解释:Model-View-Controller
根据苹果官方的解释我们可以总结出MVC分别用来实现那些代码:
Model :负责业务逻辑、来自UI数据处理、本地数据、网络接收数据。 View :负责实现屏幕展示的UI、响应用户事件。 Controller :负责View与Model间的消息的转发传递。
所以,Model继承自NSObject,View继承自UIView,Controller继承自UIViewController。
二.相互的沟通
苹果官方给出的示例图:
斯坦福公开课给出的示例图:
Model层不直接和View沟通,当Model层对象改变(比如通过网络获取到了新的数据),它会通知Controller对象,Controller对象收到通知更新对应的View。当View层有改变(比如用户通过交互创建或修改了数据,称为User Action),View会通过Controller对象去创建或修改Model层的数据。 Model层和View层是相互不知道对方的,它们的沟通是通过Controller这个中间人来协调处理。
小总结:
- Model和View永远不能相互通信,只能通过Controller传递。
- Controller可以直接与Model对话(读写调用Model),Model通过Notification和KVO机制与Controller间接通信。
- Controller可以直接与View对话,通过outlet,直接操作View,outlet直接对应到View中的控件,View通过action向Controller报告事件的发生(如用户Touch我了)。Controller是View的直接数据源(数据很可能是Controller从Model中取得并经过加工了)。Controller是View的代理(delegate),以同步View与Controller。
三.MVC的优点
(一)、低耦合性
视图层和业务层分离,这样就允许更改视图层代码而不用重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变只需要改动MVC的模型层即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则。
(二)、高重用性和可适用性
随着技术的不断进步,现在需要用越来越多的方式来访问应用程序。MVC模式允许你使用各种不同样式的视图来访问同一个服务器端的代码。它包括任何WEB(HTTP)浏览器或者无线浏览器(wap),比如,用户可以通过电脑也可通过手机来订购某样产品,虽然订购的方式不一样,但处理订购产品的方式是一样的。由于模型返回的数据没有进行格式化,所以同样的构件能被不同的界面使用。例如,很多数据可能用HTML来表示,但是也有可能用WAP来表示,而这些表示所需要的命令是改变视图层的实现方式,而控制层和模型层无需做任何改变。
(三)、较低的生命周期成本
MVC使开发和维护用户接口的技术含量降低。
(四)、可维护性
分离视图层和业务逻辑层也使得应用更易于维护和修改。
(五)、有利于软件工程化管理
由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化管理程序代码。
四.MVC的缺点
MVC的缺点是由于它没有明确的定义,所以完全理解MVC并不是很容易。使用MVC需要精心的计划,由于它的内部原理比较复杂,所以需要花费一些时间去思考。
你将不得不花费相当可观的时间去考虑如何将MVC运用到你的应用程序,同时由于模型和视图要严格的分离,这样也给调试应用程序带来了一定的困难。每个构件在使用之前都需要经过彻底的测试。一旦你的构件经过了测试,你就可以毫无顾忌的重用它们了。 根据开发者经验,由于开发者将一个应用程序分成了三个部件,所以使用MVC同时也意味着你将要管理比以前更多的文件,这一点是显而易见的。这样好像我们的工作量增加了,但是请记住这比起它所能带给我们的好处是不值一提。 MVC并不适合小型甚至中等规模的应用程序,花费大量时间将MVC应用到规模并不是很大的应用程序通常会得不偿失。 MVC设计模式是一个很好创建软件的途径,它所提倡的一些原则,像内容和显示互相分离可能比较好理解。但是如果你要隔离模型、视图和控制器的构件,你可能需要重新思考你的应用程序,尤其是应用程序的构架方面。如果你肯接受MVC,并且有能力应付它所带来的额外的工作和复杂性,MVC将会使你的软件在健壮性,代码重用和结构方面上一个新的台阶。
五.View是如何向Controller发送信息的
View对Controller的交流有三种不同的方式:
它是这样工作的,Controller会在自己的内部“悬挂”一个目标(target),如图中的红白相间的 靶子,对应的,它还会分发一个操作(action,如图中的黄色箭头)给将要和它交流的视图对象(可能是屏幕上的一个按钮),当按钮被按时,action 就会被发送给与之对应的target,这样View就可以和Controller交流了。但是在这种情况下,View只是知道发送action给对应的target,它并不知道Controller中的 类,也不知道它到底发送了什么。target-action是我们经常使用的方法。
有时候,View需要和Controller进行同步,你知道,用户交互不仅仅是什么按按钮,划滑块,还有很多种形式。好了, 让我们来看看图中的delegate黄色箭头,你发现箭头上又分出了四个小箭头:should,did,will,还有一个没标注的。绝大部分的 delegate信息都是should,will,did这三种形式。和英文意思相对应,should代表视图对象将询问Controller中的某个对象“我应该这么做 么?”,举个例子,有一个web视图,有人点击了一个链接,web视图就要问“我应该打开这个链接么?这样做安全么?”。这就是should信息。那 will和did呢?will就是“我将要做这件事了”,did就是“我已经做了这件事”。Controller把自己设置为V的委托(delegate),它让View知道:如 果V想知道更多的关于将如何显示的信息的话,就向C发送delegate信息。通过接受V发过来的delegate信息,Controller就会做出相应的协调和处理。还 有一点,每个View只能有一个delegate。
View不能拥有它所要显示的数据,记住这点非常重要。View希望别人帮助它管理将要显示的数据,当 它需要数据时,它就会请求别人的帮助,把需要的数据给它。再者,iphone的屏幕很小,它不能显示包含大量信息的视图。看图中的datasource箭 头,和delegate类似,View会发送cout,data at信息给Controller来请求数据。
对于不同的UIView,有相应的UIViewController,对应MVC中的C。例如在iOS上常用的UITableView,它所对应的Controller就是UITableViewController。
六.MVC的使用
下面我写了一个登录系统的小demo,源代码如下:MVC初使用
建好相应的文件 这里我将根视图设置为了LandViewController。
LandModel LandModel存储的是初始的账号和密码,以及后边注册的账号密码。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LandModel : NSObject
@property (nonatomic, strong) NSMutableArray *nameArray;
@property (nonatomic, strong) NSMutableArray *passArray;
- (void)landModelInit;
@end
NS_ASSUME_NONNULL_END
#import "LandModel.h"
@implementation LandModel
- (void)landModelInit {
self.nameArray = [[NSMutableArray alloc] init];
[self.nameArray addObject:@"123"];
self.passArray = [[NSMutableArray alloc] init];
[self.passArray addObject:@"123"];
}
@end
LandView
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LandView : UIView
@property (nonatomic, strong) UITextField *nameTextField;
@property (nonatomic, strong) UITextField *passTextField;
@property (nonatomic, strong) UIButton *landButton;
@property (nonatomic, strong) UIButton *registerButton;
@end
NS_ASSUME_NONNULL_END
#import "LandView.h"
@implementation LandView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor whiteColor];
self.nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(100, 200, 200, 50)];
self.nameTextField.placeholder = @"请输入用户名";
[self addSubview:self.nameTextField];
self.passTextField = [[UITextField alloc] initWithFrame:CGRectMake(100, 280, 200, 50)];
self.passTextField.placeholder = @"请输入密码";
self.passTextField.secureTextEntry = YES;
[self addSubview:self.passTextField];
self.landButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.landButton setTitle:@"登陆" forState:UIControlStateNormal];
self.landButton.titleLabel.font = [UIFont systemFontOfSize:20];
self.landButton.frame = CGRectMake(150, 400, 50, 30);
[self addSubview:self.landButton];
self.registerButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.registerButton setTitle:@"注册" forState:UIControlStateNormal];
self.registerButton.titleLabel.font = [UIFont systemFontOfSize:20];
self.registerButton.frame = CGRectMake(220, 400, 50, 30);
[self addSubview:self.registerButton];
return self;
}
@end
LandViewController
#import "LandViewController.h"
#import "LandModel.h"
#import "LandView.h"
#import "RegisterViewController.h"
@interface LandViewController ()<RegisterViewDelegate>
@property (nonatomic, assign) NSInteger flag;
@property (nonatomic, strong) LandView *landView;
@property (nonatomic, strong) LandModel *landModel;
@end
@implementation LandViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self addUI];
}
- (void)addUI {
self.landView = [[LandView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
[self.view addSubview:self.landView];
[self.landView.landButton addTarget:self action:@selector(land:) forControlEvents:UIControlEventTouchUpInside];
[self.landView.registerButton addTarget:self action:@selector(registerName:) forControlEvents:UIControlEventTouchUpInside];
self.landModel = [[LandModel alloc] init];
[self.landModel landModelInit];
}
- (void)land:(UIButton*)button {
for (int i = 0; i < self.landModel.nameArray.count; i++) {
if ([self.landView.nameTextField.text isEqualToString:self.landModel.nameArray[i]] && [self.landView.passTextField.text isEqualToString:self.landModel.passArray[i]]) {
self.flag = 1;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"登陆成功" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
}
if (!self.flag) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"输入的账号或用户名有误,请重新输入" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
self.flag = 0;
}
- (void)registerName:(UIButton*)button {
RegisterViewController *view = [[RegisterViewController alloc] init];
view.modalPresentationStyle = UIModalPresentationFullScreen;
view.delegate = self;
view.nameArray = self.landModel.nameArray;
[self presentViewController:view animated:YES completion:nil];
}
- (void)transName:(NSString *)name andPass:(NSString *)pass {
[self.landModel.nameArray addObject:name];
[self.landModel.passArray addObject:pass];
}
@end
RegisterView
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RegisterView : UIView
@property (nonatomic, strong) UIButton *back;
@property (nonatomic, strong) UIButton *sure;
@property (nonatomic, strong) UITextField *nameTextField;
@property (nonatomic, strong) UITextField *passTextField;
@property (nonatomic, strong) UITextField *againTextField;
@end
NS_ASSUME_NONNULL_END
#import "RegisterView.h"
@implementation RegisterView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor whiteColor];
self.back = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.back.frame = CGRectMake(50, 100, 50, 40);
[self.back setTitle:@"返回" forState:UIControlStateNormal];
self.back.titleLabel.font = [UIFont systemFontOfSize:20];
[self addSubview:self.back];
self.sure = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.sure.frame = CGRectMake(200, 450, 50, 40);
[self.sure setTitle:@"注册" forState:UIControlStateNormal];
self.sure.titleLabel.font = [UIFont systemFontOfSize:20];
[self addSubview:self.sure];
self.nameTextField = [[UITextField alloc] initWithFrame:CGRectMake(100, 200, 200, 50)];
self.nameTextField.placeholder = @"请输入用户名";
[self addSubview:self.nameTextField];
self.passTextField = [[UITextField alloc] initWithFrame:CGRectMake(100, 270, 200, 50)];
self.passTextField.placeholder = @"请输入密码";
self.passTextField.secureTextEntry = YES;
[self addSubview:self.passTextField];
self.againTextField = [[UITextField alloc] initWithFrame:CGRectMake(100, 340, 200, 50)];
self.againTextField.placeholder = @"请确认密码";
self.againTextField.secureTextEntry = YES;
[self addSubview:self.againTextField];
return self;
}
@end
RegisterViewController 这里使用了协议传值,将新注册的值传递给登录界面。
#import <UIKit/UIKit.h>
#import "RegisterView.h"
#import "RegisterModel.h"
@protocol RegisterViewDelegate <NSObject>
- (void)transName:(NSString*)name andPass:(NSString*)pass;
@end
NS_ASSUME_NONNULL_BEGIN
@interface RegisterViewController : UIViewController
@property (nonatomic, strong) RegisterModel *registerModel;
@property (nonatomic, strong) RegisterView *registerView;
@property (nonatomic, strong) id<RegisterViewDelegate> delegate;
@property (nonatomic, strong) NSMutableArray *nameArray;
@end
NS_ASSUME_NONNULL_END
#import "RegisterViewController.h"
@interface RegisterViewController ()
@end
@implementation RegisterViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self addUI];
}
- (void)addUI {
self.view.backgroundColor = [UIColor whiteColor];
self.registerView = [[RegisterView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
[self.view addSubview:self.registerView];
[self.registerView.back addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
[self.registerView.sure addTarget:self action:@selector(sure:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)sure:(UIButton*)button {
if ([self.registerView.nameTextField.text isEqualToString:@""] || [self.registerView.passTextField.text isEqualToString:@""] || [self.registerView.againTextField.text isEqualToString:@""]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"请输入您要注册的账号和密码" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
for (int i = 0; i < self.nameArray.count; i++) {
if ([self.registerView.nameTextField.text isEqualToString:self.nameArray[i]]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"该账号已被注册,请重新输入账号" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
}
if ([self.registerView.passTextField.text isEqualToString:self.registerView.againTextField.text]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"注册成功" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[self.delegate transName:self.registerView.nameTextField.text andPass:self.registerView.passTextField.text];
[self dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
} else {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"两次输入的密码不一致,请确认输入" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sure];
[self presentViewController:alert animated:YES completion:nil];
}
}
- (void)back:(UIButton*)button {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
效果图:
|