【iOS】MVC模式的学习
前言
笔者在暑假的学习中完成了一些小项目,这些小项目中间有时候出现了一个小bug都要寻找很久,而且会导致所有整个项目无法运行,这时候就更体现了我们一个优秀的项目需要满足的几个要求:高内聚,低耦合。 代码均摊,易于扩展,具有易用性。
我们创建一个控件,设置这个控件的样子,设置这个控件的交互方法,展示这个控件,都需要一定的代码量,控件少的时候还好,看得过去,但是控件一多,像下面这个App 的界面,各种控件就多起来了,这个时候如果还把他们都堆在一个ViewController 里面就不合适了,要改bug,要添加控件就会变得非常麻烦。
这时候我们就会发现我们的一个ViewController的代码会非常臃肿,修改代码也不方便。这时候我们就出现了一个MVC架构来解决这种代码臃肿的情况,尽可能的降低耦合度,实现一个代码均摊的效果。
MVC模式
概念
MVC是Model-VIew-Controller,就是模型-视图-控制器,MVC把软件系统分为三个部分:Model,View,Controller。其就是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- M:Model(模型)负责处理数据,以及处理部分的业务逻辑
通俗来说,就是你的程序是什么,就是你的程序将要实现的功能,或者是它所能干的事情。也就是微信消息列表里的人名字,信息内容,头像,是否屏蔽该人的消息等等数据,可以认为,Model 里面装满了这个程序的各种数据,它负责处理数据、以及处理部分的业务逻辑。
- V:View(视图)负责数据的展示和事件捕捉
通俗来说,在屏幕上你所看到的,这里有一个UITableView,TableView 里面有UILabel,UIImageView,你在屏幕上看到的组件,都可以归类为View。
- C:Controller / ViewController / VC(控制器)负责协调Model 和 View,处理大部分逻辑
它将数据从Model 层传送到View 层并展示出来,同时将View 层的交互传到Model 层以改变数据。大部分的逻辑操作(点击Button就是一种逻辑)都应该交由VC完成。
MVC的交流模式
苹果官方的图片
斯坦福大学公开课的图片
这张图展示了MVC架构中各个部分的关系,下面来介绍一下各个部分之间的联系:
- Controller与View之间可以进行交流,Controller可以通过outlet去控制View(输出口:控制器类可以通过一种特殊的属性来引用nib文件中的对象,可以把输出口看成是指向需要的对象的指针),而View通过,delegate,data source,target-action 来和Controller进行通信。
- Controller在接收到View传过来的Button的点击事件 经过判断处理后,可以选择下一步的具体操作过程,比如弹窗或者新界面的弹出。也可以交给Model处理,方法就是设置一个代理,从C将V传递的值再传递给M(比如在Model中通过判断后发布通知是否需要,Model在处理完数据之后,有时候可能会通过通知或者KVO的方式告知Controller,Model只负责通知,之后的操作由Controller决定)
- 注意这里的Model层和View层是一定不可以出现交互的。
MVC的一个简单实践
根据学长学姐的博客,我发现大部分学长都还是写了一个登陆注册的小demo来练习这个模式。
首先我们先要对相关的文件进行一个分类,根据不同的界面,划分成为不同MVC的模块
这里我们把登陆界面和注册界面分成两个部分,然后就是对于每一个层该怎么编写代码的问题了,这里以登陆界面的MVC来作为了示例
Model层
LandModel这里主要就是一个账号密码的作用。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LandModel : NSObject
@property (nonatomic, strong) NSMutableDictionary* Dictionary;
#import "LandModel.h"
@implementation LandModel
-(instancetype)init{ // 重写一下初始化的函数
if (self = [super init]) {
self.Dictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
@end
这个数据层你可以发现他仅仅包含了我们这里的一个账号密码的内容,然后重写一下初始化的函数。这里就完成了有关数据层的内容。
View层
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LandView : UIView
@property (nonatomic, strong) UITextField* nameTextField;
@property (nonatomic, strong) UITextField* passwordTextField;
@property (nonatomic, strong) UIButton* registerButton;
@property (nonatomic, strong) UIButton* landButton;
@end
NS_ASSUME_NONNULL_END
#import "LandView.h"
#define MAS_SHORTHAND
#define MAS_SHORTHAND_GLOBALS
#import "Masonry.h"
@implementation LandView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.backgroundColor = UIColor.redColor;
self.nameTextField = [[UITextField alloc] init];
self.nameTextField.backgroundColor = UIColor.whiteColor;
self.passwordTextField = [[UITextField alloc] init];
self.passwordTextField.backgroundColor = UIColor.whiteColor;
self.passwordTextField.secureTextEntry = YES;
self.nameTextField.placeholder = @"请输入用户名";
self.passwordTextField.placeholder = @"请输入密码";
self.registerButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.registerButton.titleLabel.text = @"注册";
self.registerButton.backgroundColor = UIColor.whiteColor;
[self.registerButton setTitle:@"注册" forState:UIControlStateNormal];
self.landButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.landButton setTitle:@"登陆" forState:UIControlStateNormal];
//self.landButton.titleLabel.text = @"登陆";
self.landButton.backgroundColor = UIColor.whiteColor;
[self addSubview:self.registerButton];
[self addSubview:self.nameTextField];
[self addSubview:self.passwordTextField];
[self addSubview:self.landButton];
[self.nameTextField mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(CGSizeMake(210, 40));
make.top.equalTo(200);
make.left.equalTo(100);
}];
[self.passwordTextField mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(CGSizeMake(210, 40));
make.top.equalTo(self.nameTextField).offset(60);
make.left.equalTo(self.nameTextField.left);
}];
[self.registerButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(CGSizeMake(100, 40));
make.top.equalTo(self.passwordTextField).offset(60);
make.left.equalTo(self.nameTextField.left).offset(5);
}];
[self.landButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(CGSizeMake(100, 40));
make.top.equalTo(self.passwordTextField).offset(60);
make.right.equalTo(self.nameTextField.right).offset(5);
}];
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
在这个部分我们需要重写我们的initWithFrame
这个函数,**如果在子类中重载initWithFrame方法,必须先调用父类的initWithFrame方法。在对自定义的UIView子类进行初始化操作。**然后我们在这个子类中重新完成相关内容。
Controller层
这个层用来处理有关交互的逻辑部分
#import <UIKit/UIKit.h>
#import "RegisetController.h"
#import "LandModel.h"
#import "LandView.h"
NS_ASSUME_NONNULL_BEGIN
@interface LandController : UIViewController<comfirmDelegate>
@property (nonatomic, strong) LandModel *myModel;
@property (nonatomic, strong) LandView *myView;
@end
NS_ASSUME_NONNULL_END
#import "LandController.h"
#import "RegisetController.h"
@interface LandController ()
@end
@implementation LandController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.redColor;
self.myView = [[LandView alloc] initWithFrame:self.view.bounds];
self.myModel = [[LandModel alloc] init];
[self.view addSubview:self.myView];
[self.myView.registerButton addTarget:self action:@selector(press) forControlEvents:UIControlEventTouchUpInside];
[self.myView.landButton addTarget:self action:@selector(press1) forControlEvents:UIControlEventTouchUpInside];
// Do any additional setup after loading the view.
}
-(void)press1 {
if ([self.myModel.Dictionary[self.myView.nameTextField.text] isEqualToString: self.myView.passwordTextField.text]) {
NSLog(@"登陆成功");
}
NSLog(@"%@", self.myModel.Dictionary);
NSLog(@"12");
}
-(void)press {
RegisetController* regVc = [[RegisetController alloc] init];
regVc.delegate = self;
[self presentViewController:regVc animated:YES completion:nil];
regVc.myModel.Dictionary = self.myModel.Dictionary;
NSLog(@"12");
}
-(void)comfirm:(NSString *)account andPassord:(NSString *)passord {
[self.myModel.Dictionary setValue:passord forKey:account];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
这部分内容就是对于我们的登陆注册系统的一个MVC的架构,其实实现的效果和之前的登陆注册系统区别不大,但主要的在于我们优化了我们的代码,减少了有关Controller层的代码量。让我们的代码有更好的扩展性以及更加易于维护。
实现的效果:
MVC的优点与缺点
从上面这个demo我们就可以看出他的优点就是将应用程序分离出不同的部分,降低代码的耦合度
优点:
- 通过将应用程序分离为这三个不同的组件,MVC 促进了模块化,从而可以更轻松地测试和维护应用程序。
- 职责分离还允许开发人员在应用程序的特定部分工作,而不会影响其他部分。这使得添加新功能或修改现有功能变得更加容易,而不会破坏应用程序的其他部分。
- MVC 还促进了可重用性。由于这些组件是分开的并且具有特定的职责,因此它们可以在应用程序的其他部分或完全其他应用程序中重用。这可以节省开发时间并降低应用程序的复杂性。
但是这个架构也存在缺点,MVC 架构的缺点主要是由视图层和控制器层高度耦合造成的,负面影响主要为:
- **代码过于集中。**ViewController 因为将两部分高度耦合,它将处理交互、视图更新 布局、Model 数据获取和修改、导航等几乎所有操作。
- **难以进行测试。**由于高度耦合,使得以检测功能为主的单元测试需要配合特定视图才能进行,让测试难度陡增。所以,在 MVC 中,开发者一般只对 Model 进行测试。
- **难以扩展。**在 ViewController 中添加新功能需要格外小心,高度耦合的逻辑结构增加了出错的风险,同时,由于 View 和 Controller 部分互相依赖,增加新功能不仅可能需要大量修改原有代码,也会使 ViewController 愈发笨重。
总结
笔者简单学习了一下MVC模式的相关内容,MVC架构可以让我们可以更加方便的让我们理解一个项目的工作原理,同时也更方便我们针对性的修改相关代码的内容,笔者对于这部分的内容还是比较浅显,之后有了更深的理解会补充相关内容。