美文网首页iOS 底层面试
iOS 面试锦囊之架构设计MVC、MVVM、MVP

iOS 面试锦囊之架构设计MVC、MVVM、MVP

作者: flowerflower | 来源:发表于2021-04-12 11:31 被阅读0次
    💪💪
    • 对架构是如何理解的?
    • 讲讲MVC、MVVM、MVP,讲讲你项目里是如何具体体现的?
    • Controller 的臃肿问题何解?

    一个好的架构应有的特点

    • 能把代码职责均衡的解耦到不同的功能类中,说简单一点就是 高内聚低耦合
    • 易用,维护成本低

    如何解耦???
    解决复杂性的最简单的方式就是多个实体间按照单一责任原则拆分职责。

    优点:对Controller进行搜身,将View的内部的细节封装起来了,外接不知道View的内部具体实现
    缺点:View依赖于Model,View绑定了模型

    废话不得说,我们一起来看个简单的小demo. 简单的一个列表展示,分别用三种形式展现:


    列表数据展示

    从图中我们可以看到一个列表的展示,对几乎刚入门的开发者来说都是so easy,但是我们这里研究是架构模式,是一种思想。
    Demo:

    //ViewController继承自UITableViewController
    #import "ViewController.h"
    #import "HTTPRequest.h"
    #import "MJExtension/MJExtension.h"
    #import "NewsModel.h"
    #import "NewsCell.h"
    #define  Success [resposeObject[@"status"] isEqualToString:@"000000"]
    @interface ViewController ()
    @property(nonatomic,strong)NSMutableArray <NewsModel *>*dataArr;
    
    @end
    @implementation ViewController
    
    #pragma mark - viewDidLoad
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self loadDataFormNetwork];
    }
    
    #pragma mark - loadDataFormNetwork
    - (void)loadDataFormNetwork{
        __weak typeof(self) weakSelf = self;
        [HTTPRequest GET:@"http://www.mocky.io/v2/5deb73662f0000690007e246" parameters:nil success:^(id  _Nonnull resposeObject) {
            
            if (Success) {
                weakSelf.dataArr = [NewsModel mj_objectArrayWithKeyValuesArray:resposeObject[@"data"]];
                [weakSelf.tableView reloadData];
            }
        } failure:^(NSError * _Nonnull error) {  }];
    }
    
    pragma mark - <UITableViewDataSource,UITableViewDelegate>
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        return self.dataArr.count;
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewsCellID" forIndexPath:indexPath];
        cell.model= [self.dataArr objectAtIndex:indexPath.row];
        return cell;
    }
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        return [self.dataArr objectAtIndex:indexPath.row].cellHeight;
    }
    #pragma mark - setter && getter Method
    - (NSMutableArray *)dataArr{
        if (!_dataArr) {
            _dataArr = [NSMutableArray array];
        }
        return _dataArr;
    }
    @end
    
    

    Controller说明:从控制器导入的头文件中我们可以发现,控制器持有Model以及View,同时由控制器层发送网络请求,请求到数据之后将数据保存在Model中,控制器相当于Model与View的中介者。

    /****************Model.h*********************/
    #import <UIKit/UIKit.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface NewsModel : NSObject
    
    @property(nonatomic, copy)NSString *title;
    @property(nonatomic, copy)NSString *content;
    @property(nonatomic, assign)CGFloat cellHeight;
    @end
    NS_ASSUME_NONNULL_END
    
    /****************Model.m*********************/
    #import "NewsModel.h"
    @implementation NewsModel
    - (CGFloat)cellHeight{
        
        if(_cellHeight == 0){
            CGFloat titleHeight  = 40;
            CGFloat bottomSpac = 10;
            NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
            attrs[NSFontAttributeName] = [UIFont systemFontOfSize:15];
            CGSize maxSize = CGSizeMake([UIScreen mainScreen].bounds.size.width - 2 * 12, MAXFLOAT);
            CGSize sizeToFit =    [self.content boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
            _cellHeight += sizeToFit.height + titleHeight + bottomSpac;
        }
        return _cellHeight;
    }
    @end
    

    Model说明:存放数据,并将cellHeight的高度进行保存,因为请求之后我们将数据保存到了模型中,从而我们知道模型中有哪些数据,从而对高度进行提前缓存。

    /****************NewsCell.h*********************/
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    @class NewsModel;
    @interface NewsCell : UITableViewCell
    @property(nonatomic,strong)NewsModel *model;
    @end
    
    NS_ASSUME_NONNULL_END
    
    /****************NewsCell.m*********************/
    #import "NewsModel.h"
    @implementation NewsCell
    - (void)setModel:(NewsModel *)model{
        _model = model;
        self.textLabel.text  = model.title;
        self.detailTextLabel.text = model.content;
    }
    @end
    
    

    View说明:简称UI层,负责显示数据,当View层状态发生改变时,会反击给控制器。


    文字解读:
    控制器用了Presenter,Presenter去处理UI以及业务逻辑,相当于取代了控制器的角色,从而又对控制器再次搜身,


    MVVM

    图片.png

    共同点:跟MVP雷同,View和Model的很多逻辑都交给了VieModel处理
    不同点:属性监听和绑定的问题,View可以监听ViewModel里面数据的改变,一旦发生改变,View的数据会自动更新(配合RAC更强大),不过笔记认为RAC这个框架体积过大,笔者觉得没必要用。
    核心:View和ViewModel是双向绑定的,ViewModel可以负责创建拥有这个View,View又可以拥有这个ViewModel

    MVC、MVVM、MVP就论绪到这里!!!
    经过上面的讲解,相信你对Contoller的臃肿问题已经有了一些自己的见解。


    对Controller 的臃肿问题,首先我们要先思考 什么样的内容才应该放到 Controller 中?,在我看来Controller 里面就只应该存放这些不能复用的代码

    • Controller 的臃肿问题何解?
      1、对于列表复杂的以及业务逻辑较多的可以将DataSource以及Delegate分离到另外一个类中
      2、将数据请求和业务逻辑分离到另外一个类中
      3、将拼装控件的逻辑,分离到另外一个类中

    题外话
    面试官打开自己的App,然后问你如果是你,你会怎么架构?

    可以这样解答


    图片.png

    业务层也就是服务层
    我个人对于逻辑的抽取,有以下总结。
    可以将网络请求抽象到单独的类中
    把网络请求封装成对象其实是使用了设计模式中的 Command 模式,它有以下好处:

    • 将网络请求与具体的第三方库依赖隔离,方便以后更换库。万一哪天第三库作者不维护了,更换起来容易
    • 方便在基类中处理公共逻辑。eg:统一处理公共参数(版本号、token、以及终端平台等等)笔者之前处理的单点登录以及异地登录。可以直接在封装的网络底层进行统一处理
      业务层我们可以负责处理登陆、以及支付模块的逻辑、订单的逻辑等等

    总结

    不要纠结于使用MVC、MVP还是MVVM,也不要为了装的显得逼格高一点而大材小用(eg:笔者讲的🌰,不过最主要在于是思想)。之所以会产生一些新的架构模式,是因为有些大型项目的业务逻辑越来越复杂,从而才会繁衍出新的花样,当然找到合适的才是最主要的!

    相关文章

      网友评论

        本文标题:iOS 面试锦囊之架构设计MVC、MVVM、MVP

        本文链接:https://www.haomeiwen.com/subject/glkugctx.html