写在前面
MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观
- 控制器(Controller)–> 负责转发请求,对请求进行处理。
- 视图(View) –> 界面设计人员进行图形界面设计。
- 模型(Model) –> 程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。
以上出自维基百科资料,下面说点人为描述(简单易懂的)~
- Model层: 数据处理层,包括网络请求,数据加工
- View层: 所有App上看得到的界面
- Controller层: Model 与 View层的中介,把Model数据在View上展示出来
- 目的: 低耦合,可复用
先看这张图,这张图是iOS的MVC架构中最经常出现的图了吧,因为IOS中的Controlller 是 UIViewController,所以导致很多人会把视图写在Controller中,如下图:
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//setupUI
//1.createView
UIView *view = [[UIView alloc]init];
view.frame = CGRectMake(100, 100, 100, 100);
view.backgroundColor = [UIColor orangeColor];
[self.view addSubview:view];
//2.createButton
UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn.center = self.view.center;
[self.view addSubview:btn];
//3...
}
这种写法在我刚蹭到iOS的时候也这样写过,先说这样写的好处,以及初学者为什么会这么写:
- 比如按钮,可以在当前控制器直接add target:添加点击事件,在当前控制器内就能调用到点击方法,不需要设置代理之类的;
- 比如要找某个界面,直接切到这个界面对应的controller就行,因为View 写在 Controller里面,不用去别的地方找,就这里有;
- 比如一个View,里面有一张图片,图片依赖于网络资源,这样写的好处,可以直接让 View 在 Controller 中就能拿到资源,不需要传值
缺点!!:
- 导致Controller特别臃肿,里面代码特别多,视图一复杂起来,代码量可能过1000行,不好维护
- 写在Controller里无法复用,除非你在 VC2里面 copy 当前VC中的 View的代码
- 特别low!!会被懂架构的人瞧不起,喷你根本不是MVC,是MC架构,可能还要你来段喊麦证明一下自己(-。-)
如何告别MC模式,真正走到MVC?
- 先给自己洗脑,iOS的Controller不是UIViewController,而是普通的Controller,没有View。(很关键的一步)
- 模块化划分,每个模块对应自己的一个View,例如Demo2模块,View层里面有个Demo2View,将界面元素写到View中
知识点1 如何传值
//View
+ (instancetype)viewWithTitleStr:(NSString *)titleStr{
//do createView
//...
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
/*setupUI*/
//1.createView - 参数通过`View`的函数作为外部参数传进去
DemoView *view = [DemoView viewWithTitleStr:@"我是参数"];
[self.view addSubview:view];
}
知识点2 控件点击事件如何回调给控制器
//View
@implementation DemoView
- (instancetype)initWithTitleStr:(NSString *)titleStr{
if (self = [super init]) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeInfoDark];
[self addSubview:btn];
[btn addTarget:self action:@selector(p_clickBtn:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)p_clickBtn:(UIButton *)sender{
//通过代理回调
[_delegate respondsToSelector:@selector(clickBtn:)] ?
[_delegate clickBtn:sender] : nil;
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//setupUI
//1.createView
DemoView *view = [DemoView viewWithTitleStr:@"我是参数"];
view.delegate = self;
[self.view addSubview:view];
}
#pragma mark - privateDelegate
- (void)clickBtn:(UIButton *)sender{
//View层按钮的点击事件回调~
}
接下来看这张iOS MVC架构图二,这张也是特别常见,在上面解决了View层之后,我们来看下这里的Model层~
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//刷新tableView
_datas = responseObject;
[_tableView reloadDatas];
} failure:nil];
}
这种写法在我刚蹭到iOS的时候又这样写过,先说这样写的好处,以及初学者为什么会这么写:
- 简单,网络请求完,直接在当前控制器刷新TableView的数据源
- 比如要找某个界面的网络请求,直接切到这个界面对应的controller就行,因为数据请求 写在 Controller里面,不用去别的地方找,就这里有;
- 比如当前网络请求接口,需要外部参数,比如前一个界面的uuid,这样写的好处,可以直接让当前请求在 Controller 中就能拿到资源,不需要传值
缺点!!:
- 又导致Controller特别臃肿,里面代码特别多,如果当前控制器需要多次请求,代码量可能过1000行,不好维护
- 写在Controller里无法复用,除非你在 VC2里面 copy 当前VC中的 网络请求的代码
- 如果某些接口有依赖要求,接口1请求完再请求接口2,嵌套起来,辣眼睛的程度差点治好我多年的近视
- 特别low!!会被懂架构的人瞧不起,喷你根本不是MVC,如果你还用了上面的View写在Controller的操作的话,恭喜你,最终大法 - Controller架构顺利完成,并不需要什么Model && View
如何告别VC模式,真正走到MVC?
- 不用洗脑,给自己一个大耳刮子让自己清醒清醒,这iOS的Controller就算是UIViewController,也没看到M啊,没有Model。(很关键的一步)
- 模块化划分,每个模块对应自己的一个Model,例如Demo2模块,View层里面有个Demo2Model,将网络请求&&数据处理写到Model中
知识1:如何传值(参数)
@implementation DemoModel
+ (NSArray *)fetchDatasWithUUid:(NSString *)uuid{
//Model发送网络请求
NSDictionary *parameters = @{@"uuid":uuid}
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//这是异步请求,无法return array
} failure:nil];
}
知识2:如何回调(网络请求是异步请求) - 通过Block
//Model
@implementation DemoModel
+ (void)fetchDatasWithUUid:(NSString *)uuid success:(successBlock)block{
//Model发送网络请求
NSDictionary *parameters = @{@"uuid":uuid}
[[AFHTTPSessionManager manager]GET:url
parameters:parameters
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
//通过block异步回调~
block(responseObject);
} failure:nil];
}
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas
[DemoModel fetchDatasWithUUid:_uuid success:^(NSArray *array) {
_datas = array;
[_tableView reloadDatas];
}];
}
基础的MVC讲解完毕,其实本质上就是让Controller减压,不该控制器管的他别让他知道,如上基础MVC操作之后的优势:
- MVC架构分明,在同一个模块内,如果视图有问题,找到该模块的View就行,其他同理,Controller代码大大减少,负责View的代理事件就可以
- 可以复用,比如你一个产品列表的数据,首页也要用,产品页也要用,直接分别在其对应的VC1 && VC2 调用函数[ProductModel fetchDatas]即可,无需写多次,View的复用同理
- 结构分明,便于维护,拓展也是在此基础上拓展,代码干净简洁。
进阶讲解 - MVC 配合 继承,进阶提高效率
常用的方法,抽一个基类出来,继承是子类可以拥有父类的方法,重新父类的方法即可,无需声明
//数据基类 @interface MNBaseDatas : NSObject //请求数据成功 typedef void (^MNsuccessBlock)(NSArray *array); + (void)fetchDatasSuccessBlock:(MNsuccessBlock)block; + (void)fetchDatasSuccessBlock:(MNsuccessBlock)block failureBlock:(MNfailureBlock)failure;
如果,如果抽出一个数据模型的基类,比如这里的MNBaseDatas,如之前我们举例的DemoModel就无需声明
@interface DemoModel : MNBaseDatas
/**继承自MNBaseDatas,父类有的就可以不用声明,这里的block 和 类方法都可以不用声明*/
//typedef void (^successBlock)(NSArray *array);
//+ (void)fetchDatasSuccessBlock:(MNsuccessBlock)block;
@end
//Controller
@implementation DemoViewController
- (void)viewDidLoad {
[super viewDidLoad];
//loadDatas - DemoModel没有声明 -fetchDatasSuccessBlock,一样可以调用,因为父类有此方法
[DemoModel fetchDatasSuccessBlock:^(NSArray *array) {
_datas = array;
[_tableView reloadDatas];
}];
}
如果父类没有的方法或属性,在子类里面写就行了,不会影响到父类代码,父类一般也是放公共,常用的方法(或属性),如果是特殊的,直接在子类里面新增即可,无需添加到父类~
控制器也可以使用继承,可以减少不少冗余代码
//基类控制器
@interface MNBaseViewController : UIViewController
@property (nonatomic, weak)UITableView *tableView;
@property (nonatomic, copy)NSArray *datas;
- (void)setupUI;
- (void)loadDatas;
@end
//MNBaseViewController.m 文件
@interface MNBaseViewController ()
<
UITableViewDelegate,UITableViewDataSource
>
#pragma mark - setupUI
- (void)setupUI{
//统一创建tableView,设置当前代理=self
UITableView *tableView = [[UITableView alloc]init];
tableView.frame = Frame(0, DefaultNaviHeight, ScreenW, ScreenH - DefaultNaviHeight);
tableView.delegate = self;
tableView.dataSource = self;
}
- 根据我们的封装,基本上所有的控制器都需要设置界面 setupUI 获取数据 loadDatas,所以将这两个函数抽到基类MNBaseViewController 中
- 因为iOS中,tableView应该算最常见的控件之一,基本上大多数界面都会用它展示数据,所以tableView也抽到基类中,当公告属性
- 有tableView 就跑不了数据源了,datas 同理,也抽到基类
- 同时,设置MNBaseViewController成为 tableView 的delegate和dataSource,所有的子类都无需再声明
- 如果有需要用到tableView的,一个[super setUI]就能拥有这个tableView,无需创建
这样,所有的UIViewController,只要继承自MNBaseViewController的,都可以有如上的函数和方法(可以根据需要扩充)