项目Demo
“MVC”,即Model(模型),View(视图),Controller(操控器)。
怎么规划一个程序的结构,这是一门专门的学问,叫做”架构形式“(architectural pattern),属于编程的办法论。MVC 形式便是架构形式的一种。
它是Apple 官方推荐的 App 开发架构,也是一般开发者最早遇到、最经典的架构。
1 为什么要学习并运用架构形式?
首要,作为一名合格的程序猿,咱们在写代码的时分都应该寻求美。比方在敲每一行代码的时分,都应该注重代码标准,写出一份看得舒畅,让他人也看得懂的代码,这样也能进步功率;比方在规划代码的时分,应该寻求的是我怎样才干写出一个好的架构(App 架构类似于现代修建的脚手架或者是地基,一旦确定,剩下的作业便是在现成的App里添砖加瓦),让我的代码模块化,分工清晰,从而进步我的作业功率。
- 总结一下什么是好的架构:高内聚,低耦合。 代码均摊,易于扩展,具有易用性。
在长期的写代码、犯错、改正、重构的进程中,会真实领悟到这句话的意义。
能够说,代码标准,架构形式,规划形式是检验一个程序猿水平的重要参考。
期望咱们都能写出高雅的代码!
看完了头顶的天空和方向,咱们来看看脚下的土地。
咱们之前主要是学习UI,咱们尽力让这些控件更好,更和谐的呈现在屏幕上,这很重要。
可是,咱们创立一个控件,设置这个控件的姿态,设置这个控件的交互办法,展现这个控件,都需求必定的代码量,控件少的时分还好,看得曩昔,可是控件一多,像下面这个App 的界面,各种控件就多起来了,这个时分假如还把他们都堆在一个ViewController 里边就不适宜了,要改bug,要增加控件就会变得非常费事。
而且,市面上的简直悉数的App 都是需求联网运转的,由于联网会得到数据,能够说,没有网络数据的App 是死的,只有有了网络数据,这个App 才干真实“活”起来。那由此发生的一些问题比方,咱们要怎样通过代码恳求网络,得到数据后又要怎样进行处理?
而MVC架构,便是用来处理这些问题的。
我会以微信App 为demo,了解MVC,而且简略的运用MVC 形式。
2 MVC简略介绍
MVC形式的基本思维:将应用程序分红三个部分,使它们各自担任不同的功用。MVC 是一种软件架构形式,它的目的是将应用程序的数据、用户界面和操控逻辑分离开来,以便各个部分能够独立地演化和修正。
- M:Model(模型)担任处理数据,以及处理部分的事务逻辑
浅显来说,便是你的程序是什么,便是你的程序将要实现的功用,或者是它所精干的作业。也便是微信音讯列表里的人姓名,信息内容,头像,是否屏蔽该人的音讯等等数据,能够认为,Model 里边装满了这个程序的各种数据,它担任处理数据、以及处理部分的事务逻辑。
- V:View(视图)担任数据的展现和事件捕捉
浅显来说,在屏幕上你所看到的,这儿有一个UITableView,TableView 里边有UILabel,UIImageView,你在屏幕上看到的组件,都能够归类为View。
- C:Controller / ViewController / VC(操控器)担任协调Model 和 View,处理大部分逻辑
它将数据从Model 层传送到View 层并展现出来,同时将View 层的交互传到Model 层以改动数据。大部分的逻辑操作(点击Button便是一种逻辑)都应该交由VC完结。(有少部分的逻辑处理交由Model 完结,这是下文中我要说到的胖Model 和瘦Model)
另外,一个程序里边可能不止一个VC,一个VC 里边也能够有多个View 和Model,View 和Model 也不是一一对应的联系。
浅显来说,便是怎么使你的模型呈现给用户,比方让View 上呈现Model 的数据,便是Controller 的作业。所以你能够把Controller 看成是连接Model 和View 的桥梁。
⚠️Model 和 View 是彼此独立的
这是很容易犯错的一点,由于MVC 架构形式是在软件规划中通用的,不只是iOS 开发。iOS 开发中的MVC形式是基于传统的MVC 架构的,只是在详细实现上有所不同。Apple 官方对于iOS开发中的MVC 形式和传统的MVC 架构有所不同,假如查阅传统的MVC 架构会发现,View 和Model 之间是有通讯的。
关于为什么Model和View要彼此独立: iOS开发基础:苹果的MVC形式
来自Apple官方的MVC图解
胖Model 和瘦Model
咱们刚刚说了Model有时分不可是数据源,有时分也会处理部分的事务逻辑。这种情况便是当Model 里边有许多原始数据,但View期望展现的数据是通过加工的数据,那么这个加工的进程到底应该放在VC里边仍是Model里边呢,来举个栗子阐明:
View想展现今日的日期,Model拿到的原始数据是20221124,可是View期望展现的数据是2022年11月24日。
所以不难想象,20221124
这串数字需求通过必定的加工,才会变成咱们想要的2022年11月24日
。
可是这个加工进程应该放在哪里呢?是VC仍是Model里边?
开发者们也思考过这个问题,因而发生了胖Model (Fat Model) 和瘦Model (Thin Model)
- 胖Model对应的是瘦的VC(Skinny Controller),在Model 中 对数据进行处理 ,让Controller能够直接运用通过处理后的数据。
- 瘦Model对应的是胖的VC(Fat Controller),Model中的数据 不进行任何处理或修正 ,原封不动的把服务器回来内容发送给Controller。
仍是用刚刚的栗子阐明,胖Model对应的是把这个加工进程放在Model里边(所以Model胖了),相反,瘦Model便是把加工进程放在VC里边。
这两种形式都有各自的利弊,下面这篇文章中有详细介绍:你真的了解MVC吗?
这儿也给出Apple官方对MVC的介绍 Model-View-Controller
斯坦福大学公开课对MVC的介绍视频:「斯坦福大学ios开发」
图解
3 简略运用MVC
3.1 建立MVC结构
如图,一个最简略的MVC架构:
3.2 View
View视图是由一个个控件组成的吗,咱们先在View里边创立一个个控件吧
由所以逆向开发,所以咱们先来观察咱们的方针:一个TableView,里边含有多个TableViewCell,那咱们来观察每个cell:
能够看到,每个cell其实里边的控件仍是许多的,在这个时分,咱们用官方的cell的装备就现已不能满足咱们的需求了,所以咱们能够想到,运用自定义cell:这便是为什么刚刚在建结构的时分咱们选择承继自UITableViewCell 像UI控件的初始化,颜色,字体,字号,位置都应该在View中设置 先来写出咱们需求的控件,由于咱们需求在VC中对其进行内容赋值,所以暴露在.h文件中
// FirstPageTableViewCell.h
@interface FirstPageTableViewCell : UITableViewCell
@property (nonatomic, strong) UILabel *personLab;
@property (nonatomic, strong) UILabel *tLab;
@property (nonatomic, strong) UIImageView *imgView; // 不要命名为image/img
@property (nonatomic, strong) UILabel *dateLab;
@property (nonatomic, strong) UIImageView *bellImageView;
@property (nonatomic, strong) UIView *separator;
@end
接下来咱们在.m文件中重写其初始化办法,而且把懒加载也都写上(这儿只举imgView
一个比如)
// FirstPageTableViewCell.m
@implementation FirstPageTableViewCell
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = UIColor.whiteColor;
[self.contentView addSubview:self.imgView];
[self.contentView addSubview:self.personLab];
[self.contentView addSubview:self.tLab];
[self.contentView addSubview:self.dateLab];
[self.contentView addSubview:self.separator];
[self.contentView addSubview:self.bellImageView];
}
return self;
}
#pragma mark - Method
- (void)setPosition {
[self.imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(58, 58));
make.centerY.equalTo(self.contentView);
make.left.equalTo(self.contentView).offset(20);
}];
}
#pragma mark - Getter
// 懒加载
- (UIImageView *)imgView {
if (_imgView == nil) {
_imgView = [[UIImageView alloc] init];
_imgView.frame = CGRectMake(20, 15, 58, 58);
_imgView.layer.masksToBounds = YES;
_imgView.layer.cornerRadius = 5;
}
return _imgView;
}
@end
3.3 Model
首要是.h文件,Model应该是反响数据的当地,因而Model里边应该设定和上面的数据类型相同的特点
下面的FirstPageModelWithDic
办法则是反映了Controller与Model之间的通讯,这个办法会回来一个含有数据的model,Controller将会接收这个model,以便后边供View展现。
// FirstPageModel.h
@interface FirstPageModel : NSObject
@property (nonatomic, copy) NSString *person;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSString *image;
@property (nonatomic, copy) NSString *date;
/// 是否屏蔽此人音讯
@property (nonatomic) NSNumber *bell;
/// 字典转模型
/// @param dic 字典
- (instancetype)FirstPageModelWithDic:(NSDictionary *)dic;
@end
在FirstPageModelWithDic
办法中,咱们运用了KVC 的办法字典转模型
/// KVC字典转模型
/// @param dic 字典
- (instancetype**)FirstPageModelWithDic:(NSDictionary *)dic {
[self setValuesForKeysWithDictionary:dic];
return self;
}
最好再加上避免溃散
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
- (id)valueForUndefinedKey:(NSString *)key {
return nil;
}
3.4 Controller
Controller 是整个MVC结构中最重要的一环,它操控着View 和Model,与它们坚持直接通讯
因而,咱们第一步便是把View 和Model import进来
能够回想到,咱们之前创立View 和Model 的进程中,都是没有在View中 import Model,在Model 中 import View 的,由于它们是独立,不直接通讯的。
3.4.1 从Model里边拿到数据
这次的数据是从plist 文件中取得,首要由于咱们的方针是音讯列表,里边有许多条相像的数据,所以数据的存储方法应该为里边存放了Model 的数组NSArray
/// 数据
@property (nonatomic, strong) NSArray<FirstPageModel *> *dataArray;
- (NSArray<FirstPageModel *> *)dataArray {
if (_dataArray == nil) {
// 从plist文件中加载
NSString *path = [[NSBundle mainBundle] pathForResource:@"firstPageData.plist" ofType:nil];
NSArray *dataArray = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *ma = [NSMutableArray array];
for (NSDictionary *dic in dataArray) {
FirstPageModel *model = [[FirstPageModel alloc] init];
[model FirstPageModelWithDic:dic];
[ma addObject:model];
}
_dataArray = ma;
}
return _dataArray;
}
3.4.2 操控着View
由于咱们主要的View 是TableView,咱们能够通过UITableViewDataSource
和UITableViewDelegate
来与View 坚持通讯
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataArray.count;
}
Controller 把从Model 那里拿到的数据给View 展现,这就完结了数据从Model 到Controller 再到View的进程
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
FirstPageModel *dataModel = self.dataArray[indexPath.row];
// 复用
static NSString *firstPageCellID = @"firstPageCellID";
FirstPageTableViewCell *firstCell = [tableView dequeueReusableCellWithIdentifier:firstPageCellID];
if (firstCell == nil) {
firstCell = [[FirstPageTableViewCell alloc] init];
// VC把从Model那里拿到的数据给View展现
firstCell.personLab.text = dataModel.person;
firstCell.tLab.text = dataModel.text;
firstCell.imgView.image = [UIImage imageNamed:dataModel.image];
firstCell.dateLab.text = dataModel.date;
// 是否屏蔽信息
// 把NSNumber转换为NSInteger
// 把对数据的处理放在了Controller里边,这种是瘦Model,胖VC的做法
NSInteger isBell = [dataModel.bell integerValue];
if (!isBell) { // 假如不屏蔽,就把屏蔽的图案移除
[firstCell.bellImageView removeFromSuperview];
}
//设置cell无法点击
[firstCell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
return firstCell;
}
当然,还有代码没有展现完,可是最能体现MV 思维的现已呈现完结。完好的代码请看Demo
4 其他重要的架构
前面说了,MVC 架构是最基本的架构,是一般开发者最早遇到的架构,可是随着事务和逻辑的不断增多,MVC 的一些例如Controller 代码臃肿等坏处也逐步闪现,这时分MVP,MVVM,VIPER等架构也应运而生,这些架构在公司里边的实际开发中经常被运用。
5 项目Demo
WeChat-MVC-github