MVC模式是iOS编程中提到的最多次的设计模式,也是使用最频繁的设计模式之一。网络上有很多的MVC模式的分析文章,但都是从原理上来解释,很少能找到配套的案例来说明到底在实际的项目中要如何的使用这种模式。小编在经过详细的研究、对比和实验了之后,总结了一下这个模式的一些简单使用方法,希望能起一个抛砖引玉的作用,使得对MVC默认的同学能依葫芦画瓢的了解MVC模式的使用方法,并以此类推出更多、更好的方法出来。
这篇文章先从老生常谈的MVC设计模式的原理说起,然后配上一个简单的案例,以演示如何将一个常规的编程改为使用MVC模式来编程。
本文假定,您已经熟悉了target-action模式、delegate模式、Notification或KVO模式。如果这几个模式还不是很了解,可参考网络上其它文章说明,本文不会就这些模式做详细讲解。
一、什么是MVC?
MVC是model-view-control的简称。在解释这个名词之前,我们先假定一个案例,在这个例子中,有两个文本框T1和T2、两个按钮B1和B2,B1为保存按钮,B2为加载按钮;T1为文本内容编辑按钮,当点击B1时,将T1里面的内容保存到数据库中;当点击B2时,将数据库中保存的内容显示到T2上。为了验证这个操作是否是经过数据库了,我们可以做一个操作,比如将T1里面的内容后面加上一些字符、符号等(这个相信很好做吧,你可以在将T1的数据保存到数据库的时候做这个操作,也可以在将数据从数据库中调出、显示在T2上之前做这个操作,本案例不在详细叙述)。当我们脑海里形成好了这样一个应用之后,下面我们来看看M、V、C到底做什么样的工作:
View——顾名思义,就是存放视图使用的。对应上面的例子,我们应该把T1、T2、B1和B2放在View上,对吧?
Model——即模型。模型一般都有很好的可复用性,统一管理一些数据。在上面的例子中,数据库是不是可以作为一个模型呢?答案是肯定的。所以,我们就把数据库的所有操作都放在Model里面执行——包括但不限于数据库的创建、插入、查询、更新和删除(为啥都放一起?地球人都知道。。。)
Controller——控制器,充当一个CPU的功能,即该应用程序所有的工作都由Controller统一调控。它负责处理View和Model的事件。具体怎么调控和处理?在下面的MVC原理里面,我们将详细讲解。
MVC模式能够完成各司其职的任务模式,由于降低了各个环节的耦合性,大大优化Controller的代码量,当程序调试时,如果某一个功能没有按照既定的模式工作,可以很方便的定位到到底是Controller还是View还是Model出了问题,而且还利于程序的可复用性,建议在程序中能多多使用这个模式。
二、MVC的原理
上面的内容中,已经详细描述了model、view和controller之间如何各司其职(即该是谁的东西,谁就要保护好,不能让另外一个越俎代庖的去处理)。MVC模式虽然是iOS编程中使用最广泛的模式,但论起复杂程度,MVC模式可以算是众多设计模式之首。通常情况下,MVC模式需要综合使用target-action模式、delegate模式、Notification或KVO模式等。下图是斯坦福大学的iOS一堂关于iOS介绍的公开课上所使用的示例图,这张图像也生动的描绘出来了MVC模式的工作原理,接下来的原理讲解也是依托于这张图像:
1、 Controller和View之间可以通信,Controllor通过outlet(输出口)控制View,View可以通过target-action、delegate或者data source(想想UITableVeiwDatasource)来和Controller通信;
2、 Controller在接收到View传过来的交互事件(View就是完成让人和程序的交互的呀,比如按B1按钮)之后,经过一些判断和处理,把需要Model处理的事件递交给Model处理(比如刚才的例子中的保存到数据库),Controller对Model使用的是API;
3、 Model在处理完数据之后,如果有需要,会通过Notification或者KVO的方式告知Controller,事件已经处理完,Controller再经过判断和处理之后,考虑下一步要怎么办(是默默无闻的在后台操作,还是需要更新View,这得看Controller的“脸色”行事)。这里的无线天线很有意思,Model只负责发送通知,具体谁接收这个通知并处理它,Model并不关心,这一点非常重要,是理解Notification模式的关键。
4、 Model和View之间不直接通信!
按照上面的原理,我们知道了M、V、C之间的各司其职——Model不保存控件,View不做数据库操作(但这个也不是绝对,如果需要View做一些数据缓存工作,还是需要保存一些临时数据的),而Controller就充当了两者之间的协调器。
如此,MVC的原理已经理出来一个头绪了,那么我们来看一个实际的例子,来验证如何使用MVC模式。在这个例子中,View通过target-action模式向Controller传递消息,Controller通过API调用Model里面的方法来处理从View那接收到的消息;Model处理完数据之后,通过Notification模式向Controller传递一个消息,最终Controller通过一个方法(即Notification的接收方法)弹出来一个对话框显示Model已经处理完成。
三、实际案例
1、 不使用MVC模式的案例:
1.1 使用Xcode创建一个Single View Application,命名为MVCsample。
1.2 在ViewController.h里面,添加如下代码:
[objc] view plaincopy
- @property (nonatomic, strong) UIButton *saveBtn; //点击该按钮,保存数据
- @property (nonatomic, strong) UIButton *loadBtn; //点击该按钮,加载数据
如下图所示:
1.3 在ViewController.m文件的- (void)viewDidLoad方法中,添加如下代码:
[objc] view plaincopy
- _saveBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- [_saveBtn setFrame:CGRectMake(50, 50, 150, 80)];
- [_saveBtn setTitle:@"保存" forState:UIControlStateNormal];
- [_saveBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //默认的页面背景色是白色,butotn上的文字的默认颜色也是白色,所以在此处将button上的文字颜色设置为黑色,以便显示
- [_saveBtn addTarget:self action:@selector(saveBtnPressed:) forControlEvents:UIControlEventTouchUpInside]; //添加target-action模式
- [self.view addSubview:_saveBtn];
- _loadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- [_loadBtn setFrame:CGRectMake(50, 160, 150, 80)];
- [_loadBtn setTitle:@"加载" forState:UIControlStateNormal];
- [_loadBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
- [_loadBtn addTarget:self action:@selector(loadBtnPressed:) forControlEvents:UIControlEventTouchUpInside]; //添加target-action模式
- [self.view addSubview:_loadBtn];
如下图所示:
1.4 在上面的一行代码中,我们分别为_saveBtn和_loadBtn添加了target-action模式,接下来在ViewController.m文件中实现这两个action方法,代码如下:
[objc] view plaincopy
- - (void)saveBtnPressed : (UIButton*)sender{
- NSLog(@"保存");
- NSLog(@"当前设备的型号:%@", [[UIDevice currentDevice]systemVersion]);
- /*
- //从iOS 8开始,苹果建议不使用UIAlertView和UIActionsheet,而是要使用UIAlertController。从iOS 9开始,UIAlertView和UIActionsheet更是已经被禁用,所以在这个案例中,虽然如下的UIAlertView方法还是可以照常运行,但我们还是遵从苹果的建议,使用新的技术和方法比较妥当
- UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"恭喜" message:@"保存成功" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
- [alert show];
- */
- UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"恭喜"
- message:@"保存成功"
- preferredStyle:UIAlertControllerStyleAlert];
- UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
- NSLog(@"点击了取消按钮");
- }];
- UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
- NSLog(@"点击了确定按钮");
- }];
- [alertController addAction:cancelAction];
- [alertController addAction:okAction];
- [self presentViewController:alertController animated:YES completion:nil];
- }
- - (void)loadBtnPressed : (UIButton*)sender{
- NSLog(@"加载");
- }
代码有点长,不再截图,各位请自行想象~~~
1.5 然后运行该程序,模拟器显示如下图:
点击了“保存按钮”后,模拟器及Xcode输出台显示如下图所示:
再点击AlertView上的“取消”或者“确定”按钮,Xcode输出台显示如下:
以上内容,就是不使用MVC模式的情况下,我们经常做得内容。下面,我们就按照MVC的原理,来将改程序按照MVC的思想重新做一遍。
2. 使用MVC模式的案例:
2.1 使用Xcode创建一个Single View Application,命名为MVCsampleWithMVC;
2.2 新建三个文件夹(Group),分别命名为M、V和C;
2.3 新建一个名为VView的类,继承自UIView,并将UIView.h和UIView.m文件拖到V文件夹下;
2.4 新建一个名为MModel的类,继承自NSObject,并将MModel.h和MModelm文件拖到M文件夹下;
2.5 将ViewController.h和ViewController.m文件拖到C文件夹下。做完2.2~2.5的工作之后,Xcode工作组应该显示如下图所示:
2.6 按照MVC的思想,V里面只存放界面显示的控件,在刚才的例子中,就是“保存”和“加载”这两个按钮,于是,我们把有关这两个按钮的代码都写到VView.h和VView.m中。
写好后的VView.h的代码如下:
[objc] view plaincopy
- #import <UIKit/UIKit.h>
- @interface VView : UIView
- @property (nonatomic, strong) UIButton *saveBtn; //点击该按钮,保存数据
- @property (nonatomic, strong) UIButton *loadBtn; //点击该按钮,加载数据
- - (void)viewInit; //添加一个方法,用于初始化控件
- @end
写好后的VView.m的代码如下:
[objc] view plaincopy
- #import "VView.h"
- @implementation VView
- - (void)viewInit {
- _saveBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- [_saveBtn setFrame:CGRectMake(50, 50, 150, 80)];
- [_saveBtn setTitle:@"保存" forState:UIControlStateNormal];
- [_saveBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; //默认的页面背景色是白色,butotn上的文字的默认颜色也是白色,所以在此处将button上的文字颜色设置为黑色,以便显示
- [self addSubview:_saveBtn];
- _loadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- [_loadBtn setFrame:CGRectMake(50, 160, 150, 80)];
- [_loadBtn setTitle:@"加载" forState:UIControlStateNormal];
- [_loadBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
- [self addSubview:_loadBtn];
- }
- @end
和上面的不使用MVC模式例子相比较,发现_saveBtn和_loadBtn都没有添加target-action方法。这是因为target-action方法需要设置一个target对象,在这个对象里调用action方法。按照MVC的思想,V里面不进行数据处理,而是要在C里面统一调控有C还是M来处理数据。本例中,我们是在C里面处理,所以我会把targe-action方法写到C里面,详细后面介绍。
2.7 按照MVC的思想,M主要用于数据的处理,我们假设这个案例是要将V中的某一段内容保存到数据库中,那么这个保存到数据库中的操作就是在M里面完成的。此处我们直接简化操作,只是写两个方法,用来提示保存和加载成功的。如此,MModel.h中的代码如下:
[objc] view plaincopy
- #import <Foundation/Foundation.h>
- @interface MModel : NSObject
- - (void)save;
- - (void)load;
- @end
MModel.m中的代码如下:
[objc] view plaincopy
- #import "MModel.h"
- @implementation MModel
- - (void)save{
- NSLog(@"保存。。。");
- [[NSNotificationCenter defaultCenter] postNotificationName:@"saveSucessful" object:self]; //使用Notification模式发送一个通知,用于通知Controller要做什么事情
- }
- - (void)load{
- NSLog(@"加载。。。");
- }
- @end
可以看到,这段代码和不使用MVC模式的代码中的target-action方法中的action方法是基本上一样的。那么我么就会有一个思路——在Controller中,当我们为按钮添加了target-action模式之后,对应要实现的action方法里面,是不是只需要调用MModel.h里面的对应的- (void)save和- (void)load方法就行了呢?完全正确!看,这就是C通过API调用M!
在- (void)save方法中,我还使用Notification模式发送了一个通知,这个通知用来告知Controller,我已经保存好数据了,接下来你看着办!在上一个不使用MVC模式的例子中,Controller是弹出来一个Alert,本例子中,我们也要实现这个功能。
2.8 V和M都已经分配好了,接下来就是看C如何协调分配了。ViewController.h中的代码如下:
[objc] view plaincopy
- #import <UIKit/UIKit.h>
- #import "VView.h"
- #import "MModel.h"
- @interface ViewController : UIViewController
- @property (nonatomic, strong) VView *aView; //实例化一个VView的对象
- @property (nonatomic, strong) MModel *mModel; //实例化一个MModel的对象,以便于调用MModel中的方法
- @end
由于我在MModel.h中定义的方法都是实例方法,所以我们只能实例化一个MModel的对象来调用这些方法。如果将MModel中的方法设置为类方法或者单例模式,就可以直接用MModel这个类来调用了。
2.9 ViewContro.m中的代码如下:
[objc] view plaincopy
- #import "ViewController.h"
- #define deviceScreenWidth [[UIScreen mainScreen]bounds].size.width
- #define deviceScreenHeight [[UIScreen mainScreen]bounds].size.height
- @interface ViewController ()
- @end
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(saveOK:)
- name:@"saveSucessful" object:nil]; //添加一个通知方法,当这个Controller接收到一个名称为@"saveSucessful"的通知后,就执行saveOK:方法
- _aView = [[VView alloc]initWithFrame:CGRectMake(0, 0, deviceScreenWidth, deviceScreenHeight)]; //初始化时一定要设置frame,否则VView上的两个按钮将无法被点击
- [_aView viewInit];
- [_aView.saveBtn addTarget:self action:@selector(saveBtnPressed:) forControlEvents:UIControlEventTouchUpInside]; //为“保存”按钮添加target-action模式
- [_aView.loadBtn addTarget:self action:@selector(loadBtnPressed:) forControlEvents:UIControlEventTouchUpInside]; //为“加载”按钮添加target-action模式
- [self.view addSubview:_aView];
- _mModel = [[MModel alloc]init];
- }
- - (void)saveBtnPressed : (UIButton*)sender{
- [_mModel save]; //调用MModel.h中的方法(API)
- }
- - (void)loadBtnPressed : (UIButton*)sender{
- [_mModel load]; //调用MModel.h中的方法(API)
- }
- - (void)saveOK : (NSNotification*) notification{
- UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"恭喜"
- message:@"保存成功"
- preferredStyle:UIAlertControllerStyleAlert];
- UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
- NSLog(@"点击了取消按钮");
- }];
- UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
- NSLog(@"点击了确定按钮");
- }];
- [alertController addAction:cancelAction];
- [alertController addAction:okAction];
- [self presentViewController:alertController animated:YES completion:nil];
- }
- - (void)didReceiveMemoryWarning {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
- }
- @end
代码里面有一些注意事项,我都用注释的形式写在了代码的后面,请读者自行研究判断,此处不再赘述,有疑问欢迎留言讨论。从C的代码中可以看到,如何在MVC中使用target-action模式(delegate模式和data source模式,暂时不在这个案例中讲述),如何在C中调用M中的API,以及M如何通过Notification模式向C发送通知并由C处理相关的通知。虽然M+V+C里面的代码总量比不使用MVC模式多了一些,但MVC模式写出来的代码层次分明,结构清楚,分工明确,为以后修改代码、调试程序都带来了极大的便利。比如你要修改显示的效果,只需要修改V中的就行,然后按照调理在C中添加相应的方法,多么明确。使用MVC模式的运行效果我就不再附图了,亲测程序能正常工作,请读者自己也试一试吧,手动写写代码,对理解代码会有很大的帮助的。
四、结语
至此,MVC模式的介绍就算结束了。可能很多读者对这么一个小程序就使用MVC模式觉得有点得不偿失,但万丈高楼平地起,只有从小处开始就能注意细节方面的事情,对以后写大型程序来讲,会非常的有帮助的。在编程界,一直流传有一个观点——一周不写代码,再写时就会觉得手生。其实,很多代码或者编程习惯,并不是靠脑袋记住的,而是靠习惯养成的。只有习惯了使用良好的设计模式,才能对设计模式的使用“聊熟于胸”,才能“信手拈来”。我也相信,MVC模式绝对不止这么一点点的思想和方法,希望有兴趣的读者朋友,能多多深入研究,多多与人探讨,将MVC的模式深挖掘,挖出它的灵魂。
本文如有纰漏,欢迎各位同仁指出并给出修改建议,为后来者提供更加准确的咨询。
——————————————————————————————————
欢迎关注我的微博:http://weibo.com/u/5750370715/home?wvr=5&c=spr_web_360_hao360_weibo_t001
欢迎关注我的微信公众号,您的支持,我的荣幸。微信搜索“登顶望峰”或者扫描如下图像二维码即可关注。我将会在公众号中不定时推送开发相关的技巧、咨询、业界新闻等。