MVVM

作者: 架构师的一小步 | 来源:发表于2019-04-05 21:31 被阅读0次
MVVM优缺点

1:任务均摊 -- MVVM 的 View 要比 MVP 中的 View 承担的责任多。因为前者通过 ViewModel 的设置绑定来更新状态,而后者只监听 Presenter 的事件但并不会对自己有什么更新。
2:可测试性 -- ViewModel 不知道关于 View 的任何事情,这允许我们可以轻易的测试 ViewModel。同时 View 也可以被测试,但是由于属于 UIKit 的范畴,对他们的测试通常会被忽略。
3:易用性 -- 在实际开发中必须把 View 中的事件指向 Presenter 并且手动的来更新 View,如果使用绑定的话,MVVM 代码量将会小的多。
MVC视图控制器太大且难以管理,这个模式提供了一个很好的代替MVC的方案,它保证了让视图控制器的轻量性。

MVVM核心 block回调通过RAC结合进行双向绑定

MVVM结构

  • M层-Model层
//
//  Model.h
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Model : NSObject


@end
//
//  Model.m
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "Model.h"

@implementation Model

@end
  • VM - ViewModel
//
//  BaseViewModel.h
//  002-MVVM
//
//  Created by Cooci on 2018/6/14.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef void(^SuccessBlock)(id data);
typedef void(^FailBlock)(id data);

@interface BaseViewModel : NSObject{
    @public
    NSString *name;
}

@property (nonatomic, copy) SuccessBlock successBlock;
@property (nonatomic, copy) FailBlock failBlock;

- (void)initWithBlock:(SuccessBlock)successBlock fail:(FailBlock)failBlock;
@end

//
//  BaseViewModel.m
//  002-MVVM
//
//  Created by Cooci on 2018/6/14.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "BaseViewModel.h"

@implementation BaseViewModel

- (void)initWithBlock:(SuccessBlock)successBlock fail:(FailBlock)failBlock{
    _successBlock = successBlock;
    _failBlock    = failBlock;
}
@end

//
//  MVVMViewModel.h
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "BaseViewModel.h"
#import <UIKit/UIKit.h>

@interface MVVMViewModel : BaseViewModel
@property (nonatomic, copy) NSString *contentKey;

@property (nonatomic, strong) NSMutableArray *dataArray;
- (void)loadData;
@end

//
//  MVVMViewModel.m
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "MVVMViewModel.h"
#import <ReactiveObjC.h>

@implementation MVVMViewModel

- (instancetype)init{
    if (self==[super init]) {
        
//        [self addObserver:self forKeyPath:@"contentKey" options:(NSKeyValueObservingOptionNew) context:nil];
    }
    return self;
}

- (void)initWithBlock:(SuccessBlock)successBlock fail:(FailBlock)failBlock{
    
    [super initWithBlock:successBlock fail:failBlock];
    
    [RACObserve(self, contentKey) subscribeNext:^(id  _Nullable x) {
        NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
        NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
        [mArray addObjectsFromArray:array];
        [mArray removeObject:x];
        
        self.successBlock(mArray);
    }];

}

- (void)loadData{
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
        // 有问题:
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.successBlock(array);
        });
    });
 
}


//#pragma mark - KVO回调
//- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//    NSLog(@"%@",change[NSKeyValueChangeNewKey]);
//
//    NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
//    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
//    [mArray addObjectsFromArray:array];
//    [mArray removeObject:change[NSKeyValueChangeNewKey]];
//
//    self.successBlock(mArray);
//}
//

//#pragma mark - dealloc
//
//- (void)dealloc{
//    [self removeObserver:self forKeyPath:@"contentKey"];
//}

#pragma mark - lazy

- (NSMutableArray *)dataArray{
    if (!_dataArray) {
        _dataArray = [NSMutableArray arrayWithCapacity:10];
    }
    return _dataArray;
}


@end
  • V层 View层
//
//  MVVMView.h
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface MVVMView : UIView

- (void)headViewWithData:(id)data;

@end

//
//  MVVMView.m
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "MVVMView.h"


#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width

 #define random(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)/255.0]
 #define randomColor random(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256))

@implementation MVVMView{
    
    NSMutableArray *dataArray;
}

- (void)headViewWithData:(id)data{
    
    NSMutableArray *dataArray = [NSMutableArray arrayWithCapacity:4];
    dataArray = data;
    
    for (int i = 0; i<dataArray.count; i++) {
        
        int row = i % 4;
        int loc = i / 4;
        CGFloat btnW = SCREEN_WIDTH/4;
        CGFloat btnH = 50;
        CGFloat btnX = row*btnW;
        CGFloat btnY = loc*btnH;

        
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setTitle:dataArray[i] forState:UIControlStateNormal];
        [btn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
        [btn setBackgroundColor:randomColor];
        btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
        btn.tag = 1000+i;
        [btn addTarget:self action:@selector(didClickBtn:) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:btn];
    }

}


#pragma mark - action

- (void)didClickBtn:(UIButton *)sender{
    
    NSInteger tag = sender.tag - 1000;
    
    NSLog(@"%zd",tag);
    
}

#pragma mark - private

/**
 通过颜色获得图片
 */
- (UIImage *)createImageWithColor:(UIColor *)color {
    
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    
    UIGraphicsBeginImageContext(rect.size);
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetFillColorWithColor(context, [color CGColor]);
    
    CGContextFillRect(context, rect);
    
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    return theImage;
    
}
@end

  • 调用层
//
//  ViewController.m
//  002-MVVM
//
//  Created by Cooci on 2018/4/1.
//  Copyright © 2018年 Cooci. All rights reserved.
//

#import "ViewController.h"
#import "MVVMViewModel.h"
#import "MVVMView.h"

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width

static NSString *const reuserId = @"reuserId";

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (nonatomic, strong) NSMutableArray *dataArray;

@property (nonatomic, strong) UITableView *tableView;

@property (nonatomic, strong) MVVMViewModel *vm;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 双向绑定  --->  数据 绑定 UI
    // 写一次  use anywhere
    
    // VC 作用: model VS UI
    __weak typeof(self) weakSelf = self;
    self.vm = [[MVVMViewModel alloc] init];
    [self.vm initWithBlock:^(id data) {
        
        NSArray *array = (NSArray *)data;
        [weakSelf.dataArray removeAllObjects];
        [weakSelf.dataArray addObjectsFromArray:array];

        MVVMView *headView = [[MVVMView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, (array.count + 1)/4*50)];
        [headView headViewWithData:array];
        weakSelf.tableView.tableHeaderView = headView;
        [weakSelf.tableView reloadData];

    } fail:nil];
    
    
    [self.view addSubview:self.tableView];
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
    

}


- (IBAction)didClickReloadDataItem:(id)sender {
    NSLog(@"点我刷新数据");
    [self.vm loadData];
}

#pragma mark - tableViewDataSource

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


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserId forIndexPath:indexPath];
    cell.textLabel.text = self.dataArray[indexPath.row];
    return cell;
}


#pragma mark - tableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    self.vm.contentKey = self.dataArray[indexPath.row];
}


#pragma mark - lazy

- (NSMutableArray *)dataArray{
    if (!_dataArray) {
        _dataArray = [NSMutableArray arrayWithCapacity:10];
    }
    return _dataArray;
}

- (UITableView *)tableView{
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.backgroundColor = [UIColor whiteColor];
        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:reuserId];
    }
    return _tableView;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end
MVVM亮点
  • 双向绑定(UI和model)
self.vm = [[MVVMViewModel alloc] init];
    [self.vm initWithBlock:^(id data) {
        
        NSArray *array = (NSArray *)data;
        [weakSelf.dataArray removeAllObjects];
        [weakSelf.dataArray addObjectsFromArray:array];

        MVVMView *headView = [[MVVMView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, (array.count + 1)/4*50)];
        [headView headViewWithData:array];
        weakSelf.tableView.tableHeaderView = headView;
        [weakSelf.tableView reloadData];

    } fail:nil];

- (void)initWithBlock:(SuccessBlock)successBlock fail:(FailBlock)failBlock{
    
    [super initWithBlock:successBlock fail:failBlock];
    
 //观察contentKey这个属性,当属性发生改变的时候
    [RACObserve(self, contentKey) subscribeNext:^(id  _Nullable x) {
        NSArray *array = @[@"转账",@"信用卡",@"充值中心",@"蚂蚁借呗",@"电影票",@"滴滴出行",@"城市服务",@"蚂蚁森林"];
        NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
        [mArray addObjectsFromArray:array];
        [mArray removeObject:x];
        
        self.successBlock(mArray);
    }];

}//当值发生改变的时候才会发生回调:(观察者模式的应用)RAC

  • 结合RAC(kvo观察者模式)
  • router路由(降低耦合,解决页面之间跳转,组件化之间的依赖)

相关文章

网友评论

      本文标题:MVVM

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