iOS-架构

2019-12-30  本文已影响0人  Imkata

架构(Architecture)就是软件开发中的设计方案。

架构可大可小,可以是处理类与类之间的关系、模块与模块之间的关系,也可以是处理客户端与服务端之间的关系。

我们经常听到的关于架构的名词:
MVC、MVP、MVVM、VIPER、CDD
三层架构、四层架构
......

一. MVC-Apple

Apple标准版的MVC如下图:

MVC-Apple.png

① Controller创建View,拥有View,并且把View添加到窗口上显示。
② View又可以通知Controller去做一些事情,比如View内部的点击事件、滚动事件都可以通知Controller,让Controller去处理这些业务逻辑。
③ Controller可以发送网络请求或者去数据库加载数据,并且Controller拥有和管理模型数据。
④ 数据发生改变,Controller是可以知道的,然后Controller会再把最新的模型数据显示到View上面去。
⑤ 可以看出,Controller是Model和View的桥梁,Model和View之间是没有任何关联的。

说到苹果版MVC,我们最熟悉的就是TableView了,可以说TableView将苹果版MVC发挥的淋漓尽致,下面我们就看一下TableView是怎么样的一个MVC结构。

MJShop:

#import <Foundation/Foundation.h>

@interface MJShop : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *price;
@end
#import "MJShop.h"

@implementation MJShop

@end

MJShopViewController:

#import <UIKit/UIKit.h>

@interface MJShopViewController : UITableViewController

@end
#import "MJShopViewController.h"
#import "MJShop.h"

@interface MJShopViewController ()

@property (strong, nonatomic) NSMutableArray *shopData;

@end

@implementation MJShopViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self loadShopData];
}

- (void)loadShopData
{
    self.shopData = [NSMutableArray array];
    
    for (int i = 0; i < 20; i++) {
        MJShop *shop = [[MJShop alloc] init];
        shop.name = [NSString stringWithFormat:@"商品-%d", i];
        shop.price = [NSString stringWithFormat:@"¥19.%d", i];
        [self.shopData addObject:shop];
    }
}

#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.shopData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NewsCell" forIndexPath:indexPath];
    
    MJShop *shop = self.shopData[indexPath.row];
    
    cell.detailTextLabel.text = shop.price;
    cell.textLabel.text = shop.name;
    
    return cell;
}

#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"1111");
}

@end
  1. 由于MJShopViewController继承于UITableViewController,UITableViewController里面已经拥有并自动创建了TableView添加到自己内部,所以①符合。
  2. TableView点击cell的事件是交给MJShopViewController的didSelectRowAtIndexPath方法处理的,所以②符合。
  3. MJShopViewController加载模型数据,并且保存到MJShopViewController里面,所以③符合。
  4. 当数据发生改变时,MJShopViewController会立马知道然后在MJShopViewController的cellForRowAtIndexPath里面更新TableView的信息展示,所以④符合。
  5. 而且可以看出,Controller是Model和View的桥梁,Model和View之间是没有任何关联的。

使用苹果版MVC的TableView可以在任何有列表的地方显示数据,因为TableView的Cell把它里面的控件都暴露出来了,如果你想使用就拿到控件进行赋值就可以了,Cell完全不关心模型是什么样的。

所以苹果版MVC的优缺点:

  1. 优点:View、Model可以重复利用,可以独立使用
  2. 缺点:Controller的代码过于臃肿

如果你也想设计一个控件想要可以在任何地方使用,也可以学习一下苹果的思想,View里面没有任何模型数据,然后把View的控件暴露出来,留给别人使用(当然,实际中很少这样用,因为麻烦)。

二. MVC-变种

苹果版MVC最大的缺点就是Controller的代码过于臃肿,所以才会有MVC-变种的出现,MVC变种之后最大的区别就是View是可以拥有模型的,如下图:

MVC-变种.png

MJApp:

#import <Foundation/Foundation.h>

@interface MJApp : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *image;
@end
#import "MJApp.h"

@implementation MJApp

@end

MJAppView:

#import <UIKit/UIKit.h>

//变种之前属性是暴露出来的
//@interface MJAppView : UIView
//@property (weak, nonatomic, readonly) UIImageView *iconView;
//@property (weak, nonatomic, readonly) UILabel *nameLabel;
//@end

@class MJApp, MJAppView;

@protocol MJAppViewDelegate <NSObject>
@optional
- (void)appViewDidClick:(MJAppView *)appView;
@end

@interface MJAppView : UIView
//变种之后View里面有个model
@property (strong, nonatomic) MJApp *app;
@property (weak, nonatomic) id<MJAppViewDelegate> delegate;
@end
#import "MJAppView.h"
#import "MJApp.h"

@interface MJAppView()

//变种之后属性不暴露
@property (weak, nonatomic) UIImageView *iconView;
@property (weak, nonatomic) UILabel *nameLabel;

@end

@implementation MJAppView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        UIImageView *iconView = [[UIImageView alloc] init];
        iconView.frame = CGRectMake(0, 0, 100, 100);
        [self addSubview:iconView];
        _iconView = iconView;
        
        UILabel *nameLabel = [[UILabel alloc] init];
        nameLabel.frame = CGRectMake(0, 100, 100, 30);
        nameLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:nameLabel];
        _nameLabel = nameLabel;
    }
    return self;
}

//变种之后内部赋值的逻辑封装起来了
- (void)setApp:(MJApp *)app
{
    _app = app;
    
    self.iconView.image = [UIImage imageNamed:app.image];
    self.nameLabel.text = app.name;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //使用代理将点击事件传递给控制器
    if ([self.delegate respondsToSelector:@selector(appViewDidClick:)]) {
        [self.delegate appViewDidClick:self];
    }
}

ViewController:

#import "ViewController.h"
#import "MJApp.h"
#import "MJAppView.h"

@interface ViewController () <MJAppViewDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建view
    MJAppView *appView = [[MJAppView alloc] init];
    appView.frame = CGRectMake(100, 100, 100, 150);
    appView.delegate = self;
    [self.view addSubview:appView];
    
    // 加载模型数据
    MJApp *app = [[MJApp alloc] init];
    app.name = @"QQ";
    app.image = @"QQ";
    
    //变种之后,设置数据到view上
    appView.app = app;
    
    //变种之前,取出控件进行赋值
//    appView.iconView.image = [UIImage imageNamed:app.image];
//    appView.nameLabel.text = app.name;
}

#pragma mark - MJAppViewDelegate
- (void)appViewDidClick:(MJAppView *)appView
{
    NSLog(@"控制器监听到了appView的点击");
}

@end

代码比较简单,可以发现变种之后,一个View对应一个Model,View的控件不再暴露出来,给View赋值的逻辑被封装在View内部了,外面使用的时候只需要给View传入一个Model就可以了。

  1. 优点:对Controller进行瘦身,将View内部的细节封装起来了,外界不知道View内部的具体实现。
  2. 缺点:View依赖于Model,不能独立使用(也不一定是坏事)。

这种MVC-变种也是开发中我们使用最多的了。

三. MVP

presenter:n. 提出者;推荐者;赠送者;任命者;主持人

可以看出P是主持工作的,它和C有点像,就相当于我们把C的一些业务逻辑交给P去做了。

MVP.png

可以发现,MVP就相当于用P代替了MVC-Apple的C,而且Model和View之间也是没有任何关联的。

MJApp:

#import <Foundation/Foundation.h>

@interface MJApp : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *image;
@end
#import "MJApp.h"

@implementation MJApp

@end

MJAppView:

#import <UIKit/UIKit.h>

@class MJAppView;

@protocol MJAppViewDelegate <NSObject>
@optional
//本来交给Controller来做的事情,现在交给P来做了
- (void)appViewDidClick:(MJAppView *)appView;
@end

@interface MJAppView : UIView
//View不拥有Model,又不想暴露控件,所以使用方法更新数据
- (void)setName:(NSString *)name andImage:(NSString *)image;
@property (weak, nonatomic) id<MJAppViewDelegate> delegate;
@end
#import "MJAppView.h"

@interface MJAppView()
@property (weak, nonatomic) UIImageView *iconView;
@property (weak, nonatomic) UILabel *nameLabel;
@end

@implementation MJAppView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        UIImageView *iconView = [[UIImageView alloc] init];
        iconView.frame = CGRectMake(0, 0, 100, 100);
        [self addSubview:iconView];
        _iconView = iconView;
        
        UILabel *nameLabel = [[UILabel alloc] init];
        nameLabel.frame = CGRectMake(0, 100, 100, 30);
        nameLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:nameLabel];
        _nameLabel = nameLabel;
    }
    return self;
}

- (void)setName:(NSString *)name andImage:(NSString *)image
{
    _iconView.image = [UIImage imageNamed:image];
    _nameLabel.text = name;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if ([self.delegate respondsToSelector:@selector(appViewDidClick:)]) {
        [self.delegate appViewDidClick:self];
    }
}
@end

MJAppPresenter:

#import <UIKit/UIKit.h>

@interface MJAppPresenter : NSObject
- (instancetype)initWithController:(UIViewController *)controller;
@end
#import "MJAppPresenter.h"
#import "MJApp.h"
#import "MJAppView.h"

@interface MJAppPresenter() <MJAppViewDelegate>
//拥有控制器,weak类型的
@property (weak, nonatomic) UIViewController *controller;
@end

@implementation MJAppPresenter

- (instancetype)initWithController:(UIViewController *)controller
{
    if (self = [super init]) {
        self.controller = controller;
        
        // 创建View
        MJAppView *appView = [[MJAppView alloc] init];
        appView.frame = CGRectMake(100, 100, 100, 150);
        appView.delegate = self;
        [controller.view addSubview:appView];
        
        // 加载模型数据
        MJApp *app = [[MJApp alloc] init];
        app.name = @"QQ";
        app.image = @"QQ";
        
        // 赋值数据
        [appView setName:app.name andImage:app.image];
//        appView.iconView.image = [UIImage imageNamed:app.image];
//        appView.nameLabel.text = app.name;
    }
    return self;
}

//本来交给Controller来做的事情,现在交给P来做了
#pragma mark - MJAppViewDelegate
- (void)appViewDidClick:(MJAppView *)appView
{
    NSLog(@"presenter 监听了 appView 的点击");
}
@end

ViewController:

#import "ViewController.h"
#import "MJAppPresenter.h"

@interface ViewController ()
//这里只有一个MJAppView,如果有新的View,就用新的presenter来处理它的业务逻辑。
@property (strong, nonatomic) MJAppPresenter *presenter;
//@property (strong, nonatomic) MJOtherPresenter *presenter1;
//@property (strong, nonatomic) MJNewsPresenter *presenter2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.presenter = [[MJAppPresenter alloc] initWithController:self];
}
@end
  1. 可以发现,Controller的大部分业务都交给presenter去做了,上面代码只有一个MJAppView,如果有新的View,就用新的presenter来处理它的业务逻辑。
  2. View不拥有Model,又不想暴露子控件,所以使用方法更新数据。
  3. View的点击事件也交给presenter处理了。
  4. 控制器和presenter互相拥有,但是presenter拥有控制器是weak类型的,防止循环引用。

四. MVVM

先看一下架构图:

MVVM.png

MJApp:

#import <Foundation/Foundation.h>

@interface MJApp : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *image;
@end
#import "MJApp.h"

@implementation MJApp

@end

MJAppView:

#import <UIKit/UIKit.h>

@class MJAppView, MJAppViewModel;

@protocol MJAppViewDelegate <NSObject>
@optional
- (void)appViewDidClick:(MJAppView *)appView;
@end

@interface MJAppView : UIView
// View拥有viewModel
@property (weak, nonatomic) MJAppViewModel *viewModel;
@property (weak, nonatomic) id<MJAppViewDelegate> delegate;
@end
#import "MJAppView.h"
#import "NSObject+FBKVOController.h"

@interface MJAppView()
@property (weak, nonatomic) UIImageView *iconView;
@property (weak, nonatomic) UILabel *nameLabel;
@end

@implementation MJAppView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        UIImageView *iconView = [[UIImageView alloc] init];
        iconView.frame = CGRectMake(0, 0, 100, 100);
        [self addSubview:iconView];
        _iconView = iconView;
        
        UILabel *nameLabel = [[UILabel alloc] init];
        nameLabel.frame = CGRectMake(0, 100, 100, 30);
        nameLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:nameLabel];
        _nameLabel = nameLabel;
    }
    return self;
}

- (void)setViewModel:(MJAppViewModel *)viewModel
{
    _viewModel = viewModel;
    
    //给ViewModel的name属性添加监听,监听到改变就更新View
    __weak typeof(self) waekSelf = self;
    [self.KVOController observe:viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
        waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
    }];
    
    //给ViewModel的image属性添加监听,监听到改变就更新View
    [self.KVOController observe:viewModel keyPath:@"image" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
        waekSelf.iconView.image = [UIImage imageNamed:change[NSKeyValueChangeNewKey]];
    }];
    
//想要监听ViewModel属性的改变,可以使用RAC,所以很多公司的MVVM是和RAC一块使用的(MVVM+RAC),但是RAC比较重,谨慎使用,这里我们使用了FaceBook的FBKVOController,自动机可以去GitHub上搜索源码。
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if ([self.delegate respondsToSelector:@selector(appViewDidClick:)]) {
        [self.delegate appViewDidClick:self];
    }
}
@end

MJAppViewModel:

#import <UIKit/UIKit.h>

@interface MJAppViewModel : NSObject
- (instancetype)initWithController:(UIViewController *)controller;
@end
#import "MJAppViewModel.h"
#import "MJApp.h"
#import "MJAppView.h"

@interface MJAppViewModel() <MJAppViewDelegate>
@property (weak, nonatomic) UIViewController *controller;
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *image;
@end

@implementation MJAppViewModel

- (instancetype)initWithController:(UIViewController *)controller
{
    if (self = [super init]) {
        self.controller = controller;
        
        // 创建View
        MJAppView *appView = [[MJAppView alloc] init];
        appView.frame = CGRectMake(100, 100, 100, 150);
        appView.delegate = self;
        // View拥有viewModel
        appView.viewModel = self;
        [controller.view addSubview:appView];
        
        // 加载模型数据
        MJApp *app = [[MJApp alloc] init];
        app.name = @"QQ";
        app.image = @"QQ";
        
        //设置数据到自己属性上面去,模型的每个属性都保存在ViewModel的属性里面了,留着给View监听。
        self.name = app.name;
        self.image = app.image;
        
        //当自己的属性改变时会被View监听到,然后View更新数据
        //self.name = @"我改变了";
    }
    return self;
}

#pragma mark - MJAppViewDelegate
//还可以处理MJAppView的点击事件
- (void)appViewDidClick:(MJAppView *)appView
{
    NSLog(@"viewModel 监听了 appView 的点击");
}

@end

ViewController:

#import "ViewController.h"
#import "MJAppViewModel.h"

@interface ViewController ()
//控制器只管理ViewModel
@property (strong, nonatomic) MJAppViewModel *viewModel;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.viewModel = [[MJAppViewModel alloc] initWithController:self];
}
@end
  1. 乍一看,MVVM和MVP有点像,它们的共同点就是MVVM也把View和Model的一些业务逻辑扔到VM里面,不会扔到控制器里面。
  2. 不同点就是属性监听绑定的问题,View拥有ViewModel并监听ViewModel里面属性的改变,当属性改变时会更新View。
  3. 想要监听ViewModel里面属性的改变,可以使用RAC,所以很多公司的MVVM是和RAC一块使用的(MVVM+RAC),但是RAC比较重,谨慎使用,这里我们使用了FaceBook的FBKVOController来监听。

五. 分层设计

先看图:

分层设计.png

上面我们说的MVC、MVP、MVVM其实都是界面层的东西,再大一点就是分层设计了,就如上图那样,不同层分别做自己的事情。

比如想加载新闻业务,代码如下:

ViewController:

#import "ViewController.h"
#import "MJNewsService.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //加载新闻数据
    [MJNewsService loadNews:@{} success:^(NSArray *newsData) {
        
    } failure:^(NSError *error) {
        
    }];
}
@end

在界面层,控制器想加载新闻业务直接加载就好了,不用关心怎么加载的。

MJNewsService:

#import "MJNewsService.h"
#import "MJHTTPTool.h"
#import "MJDBTool.h"

@implementation MJNewsService

+ (void)loadNews:(NSDictionary *)params success:(void (^)(NSArray *newsData))success failure:(void (^)(NSError *error))failure
{
    //先取出本地数据
    [MJDBTool loadLocalData....];
    
    //如果没有本地数据,就加载网络数据
    [MJHTTPTool GET:@"xxxx" params:nil success:^(id result) {
        success(array);
    } failure:failure];
}
@end

在业务层,负责新闻相关的业务,包括数据的加载等等。

MJHTTPTool:

#import "MJHTTPTool.h"

@implementation MJHTTPTool

+ (void)GET:(NSString *)URL params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
    // 调用AFN
}
@end

在网络层,负责网络数据的请求。

MJDBTool:

#import "MJDBTool.h"

@implementation MJDBTool

//加载本地数据
- (void)loadLocalData {
    
}

@end

在本地数据层,负责加载本地数据。

六. 架构与设计模式的区别

架构一般比设计模式大,比如整个应用程序分为多少层架构,比如将类分成很多角色(M、V、C、P、VM等等)这些都是架构层面的问题。

分层之后每一层肯定好多类,那么这么多类怎么组织起来呢?就可以使用设计模式 。

设计模式可以分为三大类:

  1. 创建型模式:对象实例化的模式,用于解耦对象的实例化过程
    单例模式、工厂方法模式,等等

  2. 结构型模式:把类或对象结合在一起形成一个更大的结构
    代理模式、适配器模式、组合模式、装饰模式,等等

  3. 行为型模式:类或对象之间如何交互,及划分责任和算法
    观察者模式、命令模式、责任链模式,等等

iOS中主要使用单例模式工厂方法模式代理模式观察者模式

Demo地址:架构

书籍推荐

  1. 数据结构与算法:
    严蔚敏《数据结构》
    《大话数据结构与算法》

  2. 网络:
    《HTTP权威指南》
    《TCP/IP详解卷1:协议》

架构与设计模式:https://github.com/skyming/Trip-to-iOS-Design-Patterns

上一篇 下一篇

猜你喜欢

热点阅读