项目Demo

“MVC”,即Model(模型),View(视图),Controller(操控器)。

怎么规划一个程序的结构,这是一门专门的学问,叫做”架构形式“(architectural pattern),属于编程的办法论。MVC 形式便是架构形式的一种

它是Apple 官方推荐的 App 开发架构,也是一般开发者最早遇到、最经典的架构。

1 为什么要学习并运用架构形式?

首要,作为一名合格的程序猿,咱们在写代码的时分都应该寻求。比方在敲每一行代码的时分,都应该注重代码标准,写出一份看得舒畅,让他人也看得懂的代码,这样也能进步功率;比方在规划代码的时分,应该寻求的是我怎样才干写出一个好的架构(App 架构类似于现代修建的脚手架或者是地基,一旦确定,剩下的作业便是在现成的App里添砖加瓦),让我的代码模块化,分工清晰,从而进步我的作业功率

  • 总结一下什么是好的架构:高内聚,低耦合。 代码均摊,易于扩展,具有易用性。

在长期的写代码、犯错、改正、重构的进程中,会真实领悟到这句话的意义。

能够说,代码标准,架构形式规划形式是检验一个程序猿水平的重要参考。

期望咱们都能写出高雅的代码!

看完了头顶的天空和方向,咱们来看看脚下的土地。

咱们之前主要是学习UI,咱们尽力让这些控件更好,更和谐的呈现在屏幕上,这很重要。

可是,咱们创立一个控件,设置这个控件的姿态,设置这个控件的交互办法,展现这个控件,都需求必定的代码量,控件少的时分还好,看得曩昔,可是控件一多,像下面这个App 的界面,各种控件就多起来了,这个时分假如还把他们都堆在一个ViewController 里边就不适宜了,要改bug,要增加控件就会变得非常费事。

iOS 架构设计-MVC 模式

而且,市面上的简直悉数的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图解

iOS 架构设计-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开发」

图解

iOS 架构设计-MVC 模式

3 简略运用MVC

3.1 建立MVC结构

如图,一个最简略的MVC架构:

iOS 架构设计-MVC 模式

3.2 View

View视图是由一个个控件组成的吗,咱们先在View里边创立一个个控件吧

由所以逆向开发,所以咱们先来观察咱们的方针:一个TableView,里边含有多个TableViewCell,那咱们来观察每个cell:

iOS 架构设计-MVC 模式

能够看到,每个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,咱们能够通过UITableViewDataSourceUITableViewDelegate来与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