44

iOS底层原理 MVC、MVP、MVVM、分层设计浅谈 — (13)

 5 years ago
source link: https://www.tuicool.com/articles/YvMBveM
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

苹果版本的 MVCModelVC 和交互, VCView 交互

  • 优点: ViewModel 可以重复利用,可以独立使用

  • 缺点: Controller 的代码过于臃肿

RnINZr6.png!web

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadData];
}
- (void)loadData{
    self.data=[NSMutableArray array];for (int i = 0; i < 20; i ++) {
        FYNews *item=[FYNews new];
        item.title =[NSString stringWithFormat:@"title-%d",i];
        item.name =[NSString stringWithFormat:@"name-%d",i];
        [self.data addObject:item];
    }
}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return self.data.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    
    // Configure the cell...
    FYNews *item =[self.data objectAtIndex:indexPath.row];
    cell.detailTextLabel.text =item.title;
    cell.textLabel.text = item.name;return cell;
}

//model

@interface FYNews : NSObject
@property (nonatomic,copy) NSString *title;
@property (nonatomic,copy) NSString *name;
@end复制代码

这里是 VC 中组装了 tableviewmodel 的数据在 VC 中在 view 中显示出来,当需要另外的数据的时候,只需要将 model 改成需要的 model 而无需更改 tableview 的代码兼容性较好。

MVC变种

MVC 变种,其实就是将 modelview 建立了联系, view 依据 Model 来展示数据, VC 组装 Model ,组装展示是在 view 中实现。

  • 优点:对Controller进行瘦身,将View的内部细节封装起来了,外界不知道View内部的具体实现

  • 缺点:view依赖于Model

aMJ7baJ.png!web

代码实现

//.h
@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong) FYItemModel *model;
@end

//.m
@interface FYAppleView()
@property (nonatomic,strong) UILabel *nameLabel;
@end

@implementation FYAppleView
-(instancetype)initWithFrame:(CGRect)frame{if (self =[super initWithFrame:frame]) {
        _nameLabel=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
        [self addSubview:_nameLabel];
    }return self;
}
/*
  mvc的变种
 */
- (void)setModel:(FYItemModel *)model{
    _model = model;
    _nameLabel.textColor = model.bgColor;
    _nameLabel.text = model.name;
}
@end

//FYItemModel
@interface FYItemModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) UIColor *bgColor;
@end


//ViewController
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadViewOtherMVC];
}
//变种MVC 把View和Model建立起连接
//等以后更新view数据只需要 view.model = item;Controllr少了许多代码
- (void)loadViewOtherMVC{
    FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(200, 200, 100, 30)];
    FYItemModel *item=[[FYItemModel alloc]init];
    item.name = @"校长来了";
    item.bgColor = [UIColor redColor];
    view.model = item;
    [self.view addSubview:view];
}
@end复制代码

可以看到 model 组装到 view 展示内容是在 view 实现的,外部不知道细节,只需要将 modelview 即可,但是只能传输过来 model 或者他子类,业务更改的话,需要修改 view 的内部 model 才能将变更过的数据重新展示出来。

想要监听view的点击事件来做一些操作,那么我们可以使用代理和 block ,这里 id 是实现了 FYAppleViewProtocol 协议的, weak 修饰防止循环引用,使用协议实现了和 VC 的通信。

@class FYAppleView;
@protocol FYAppleViewProtocol- (void)FYAppleViewDidClick:(FYAppleView*)view;
@end

@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;
@property (nonatomic,weak) iddelegate;
@property (nonatomic,strong) FYItemModel *model;
@end复制代码

稍作更改还是 apple-MVC

// .h
@class FYItemModel;
@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;
@end复制代码

View 属性 nameLabel 暴露出来,但是不允许外界进行更改,去掉 model 则是 MVC

MVP

MVPMVC 很像,只是将 VC 换成了 PresentervcPresent 做的事情基本一致,将 viewModel 通信改到了都和 Presenter 通信。

Yfq2yuQ.png!web

代码

//MVP
//.h
@interface FYNewsPresenter : NSObject

@property (nonatomic,weak) UIViewController *vc;
//初始化
- (void)setup;
@end

.m#import "FYNewsPresenter.h"@interface FYNewsPresenter()@end

@implementation FYNewsPresenter
- (void)setup{
	FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(200, 200, 100, 30)];
	FYItemModel *item=[[FYItemModel alloc]init];
	item.name = @"校长来了";
	item.bgColor = [UIColor redColor];
	view.model = item;
	[self.vc.view addSubview:view];
}
- (void)FYAppleViewDidClick:(FYAppleView *)view{
	NSLog(@"点击了我");
}
@end


//VC中
@interface ViewController ()
@property (nonatomic,strong) FYNewsPresenter *presenter;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
	_presenter=[FYNewsPresenter new];
	_presenter.vc = self;
	[_presenter setup];
}
@end复制代码

再次对 VC 进行了瘦身,将更多的业务逻辑搬到了 FYNewsPresenter 处理,其实全部搬过去,意义比不大, FYNewsPresenter 也会臃肿,也会出现和 VC 一样的困惑。

MVVM

MVVM 是将 FYNewsPresenter 都搬到了 FYNewsViewModel 中,然后对 FYNewsViewModelView 进行了一个双向绑定,双向绑定可以使用代理, block 或者 KVO 实现。

QN7v6bI.png!web

代码实现

@interface FYNewsViewModel : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) UIColor *bgColor;

@property (nonatomic,weak) UIViewController *vc;

- (instancetype)initWithController:(UIViewController *)vc;
@end#import "FYNewsViewModel.h"@interface FYNewsViewModel()@end
@implementation FYNewsViewModel
- (instancetype)initWithController:(UIViewController *)vc{if (self =[super init]) {
        self.vc = vc;
        
        FYAppleView * view =[[FYAppleView alloc]initWithFrame:CGRectMake(100, 200, 100, 50)];
        //    view.model = item;
        view.delegate = self;
        view.viewModel = self; //建立kvo
        
        view.backgroundColor = [UIColor lightGrayColor];
        [vc.view addSubview:view];
        
        
        
        FYItemModel *item=[[FYItemModel alloc]init];
        item.name = @"校长来了";
        item.bgColor = [UIColor redColor];
        
        self.name = item.name;
        self.bgColor = item.bgColor;
    }return self;
}
- (void)FYAppleViewDidClick:(FYAppleView *)view{
	NSLog(@"点击了我");
}
@end复制代码

view 实现

@class FYAppleView,FYNewsViewModel;
@protocol FYAppleViewProtocol- (void)FYAppleViewDidClick:(FYAppleView*)view;

@end

@class FYItemModel;

@interface FYAppleView : UIView
@property (nonatomic,strong,readonly) UILabel *nameLabel;

@property (nonatomic,weak) iddelegate;
@property (nonatomic,weak) FYNewsViewModel *viewModel;

@property (nonatomic,strong) FYItemModel *model;
@end


@interface FYAppleView()
@property (nonatomic,strong) UILabel *nameLabel;
@end
@implementation FYAppleView
-(instancetype)initWithFrame:(CGRect)frame{if (self =[super initWithFrame:frame]) {
        _nameLabel=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
        [self addSubview:_nameLabel];
    }return self;
}
/*
  mvc的变种
 */
- (void)setModel:(FYItemModel *)model{
    _model = model;
    _nameLabel.textColor = model.bgColor;
    _nameLabel.text = model.name;
	
 
}

- (void)setViewModel:(FYNewsViewModel *)viewModel{
    _viewModel = viewModel;
   [_viewModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
   //使用FBKVO实现 或者自己使用KVO实现
//    __weak typeof(self) waekSelf = self;
//    [self.KVOController observe:viewModel keyPath:@"name"//                        options:NSKeyValueObservingOptionNew
//                          block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary* _Nonnull change) {
//        waekSelf.nameLabel.text = change[NSKeyValueChangeNewKey];
//    }];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{if ([keyPath isEqualToString:@"name"]) {
        self.nameLabel.text = change[NSKeyValueChangeNewKey];
    }
}

//添加点击事件
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{	if ([self.delegate respondsToSelector:@selector(FYAppleViewDidClick:)]) {
		[self.delegate FYAppleViewDidClick:self];
	}
}

-(void)dealloc{
    [_viewModel removeObserver:selfforKeyPath:@"name"];
}
@end复制代码

使用 KVO 或者 FBKVO 或者 RAC 都是可以的,本章节例子给出了 FBKVO 或者自己使用 KVO 的实现。

分层设计

三层架构:

三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层

  • 目的: “高内聚,低耦合”的思想

  • 优点: 降低层与层之间的依赖 标准化

  • 缺点: 系统架构复杂,不适合小型项目

三层原理

3个层次中,系统主要功能和业务逻辑都在业务逻辑层进行处理。 所谓三层体系结构,是在客户端与数据库之间加入了一个 中间层 ,也叫组件层。这里所说的三层体系,不是指物理上的三层,不是简单地放置三台机器就是三层体系结构,也不仅仅有 B/S 应用才是三层体系结构,三层是指逻辑上的三层,即把这三个层放置到一台机器上。

三层体系的应用程序将业务规则、数据访问、合法性校验等工作放到了中间层进行处理。通常情况下,客户端不直接与数据库进行交互,而是通过 COM/DCOM 通讯与中间层建立连接,再经由中间层与数据库进行交互。

三层架构中主要功能与业务逻辑一般要在业务逻辑层进行信息处理和实现,其中三层体系架构中的客户端和数据库要预设中间层,成为组建层。三层架构中的三层具有一定的逻辑性,即是将三层设置到同一个计算机系统中,把业务协议、合法校验以及数据访问等程序归置到中间层进行信息处理,一般客户端无法和数据库进行数据传输,主要是利用 COM/DCOM 通讯和中间层构建衔接通道,实现中间层与数据库的数据传输,进而实现客户端与是数据库的交互

MVCMVVMMVP 属于界面层, 当业务复杂,网络请求和db操作达到了一个新的高度,界面复杂到需要好多人来做,那么界面、业务、数据需要分层了

分层之后,得到了一个三层架构或四层架构

auyAbyB.png!web

数据层也可以分为两层,分为网络请求和db层。

UZzIr2e.png!web

具体在工程中我们通常这样体现

JbayYj7.png!web

vc 中获取数据

@interface ViewController ()
@property (nonatomic,strong) FYDBPool *db;
@property (nonatomic,strong) FYHttpPool *http;
@end

@implementation ViewController
- (void)viewDidLoad {
	[super viewDidLoad];

	//当有业务层
	[[FYNewsService new] loadNewsWithInfo:nil success:^(NSArray * _Nonnull) {
		
	} fail:^{
		
	}];
	//当没有有业务层
	self.db=[FYDBPool new];
	self.http=[FYHttpPool new];
	[self.db loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {		if ([ret count]) {
			NSLog(@"数据获取成功");
		}else{
			[self.http loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {
				NSLog(@"数据获取成功");
			} fail:^{
				NSLog(@"数据获取失败");
			}];
		}
	} fail:^{
		[self.http loadNewsWithInfo:@{} success:^(NSArray * _Nonnull ret) {
			NSLog(@"数据获取成功");
		} fail:^{
			NSLog(@"数据获取失败");
		}];
	}];
}复制代码

在业务层

@interface FYNewsService ()
@property (nonatomic,strong) FYDBPool *db;
@property (nonatomic,strong) FYHttpPool *http;

@end
@implementation FYNewsService
-(instancetype)init{	if (self = [super init]) {
		self.db=[FYDBPool new];
		self.http=[FYHttpPool new];
	}	return self;
}
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock{
	[self.db loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {		if ([ret count]) {
			succblock(ret);
		}else{
			[self.http loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {
				succblock(ret);
			} fail:failBlock];
		}
	} fail:^{
		[self.http loadNewsWithInfo:info success:^(NSArray * _Nonnull ret) {
			succblock(ret);
		} fail:failBlock];
	}];
}
@end复制代码

在db层

typedef void(^succcessCallback)(NSArray *);
@interface FYDBPool : NSObject
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock;
@end复制代码

在网络请求层

typedef void(^succcessCallback)(NSArray *);
@interface FYHttpPool : NSObject
- (void)loadNewsWithInfo:(NSDictionary *)info
				 success:(succcessCallback )succblock
					fail:(dispatch_block_t)failBlock;
@end复制代码

分层目的是瘦身,逻辑清晰,业务清晰,降低耦合,当某一块足够复杂时候,都可以进行分层,不局限于网络或 db ,当 db 足够复杂,也需要进行一个分层来解决复杂调用和处理的问题。 不同的人来处理不同的分层,相互影响也比较小,降低耦合。

当逻辑层足够完善,则UI层如何变动都不需要更改逻辑层。

后记

优雅的代码总是伴随着各种传统设计模式的搭配

设计模式

设计模式(Design Pattern) 是一套被反复使用、代码设计经验的总结 使用设计模式的好处是:可重用代码、让代码更容易被他人理解、保证代码可靠性 一般与编程语言无关,是一套比较成熟的编程思想

设计模式可以分为三大类

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK