MVVM
1 - MVVM 原理
① Model-View-ViewModel 是 M-V-VM 三部分组成,它本质上是 MVC 的改进版
② MVVM 就是将其中的 View 的状态和行为抽象化,其中 ViewModel 将视图 UI 和 业务逻辑分开,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑
③ MVVM 采用双向数据绑定。View 中数据变化将自动反映到 ViewModel 上,同样 Model 中数据变化也将会自动展示在界面上。MVVM 的核心思想就是关注 Model 的变化,让 MVVM 框架利用自己的机制自动更新 DOM,也就是所谓的数据-视图分离
2 - MVVM 的好坏
① 好处
代码清晰:ViewModel 分离出来大部分的 Controller 代码,更加清晰和容易维护
方便测试:大部分 Bug 来自于逻辑处理,由于 ViewModel 把逻辑分离出来,可对 ViewModel 构造单元测试
开发解耦:一位开发者负责逻辑实现,另一位开发者负责 UI 实现
② 坏处:代码量比 MVC 多、需对每个 Controller 实现绑定,这是分离不可避免的工作量
MVVM 实战
1 - 思路:同 MVP 架构模式一样,我们需要把 VM 和 C 两者绑定在一起,目录结构如下
2 - 代码示例:重点是 TestViewModel 中的数据双向绑定
// - TestModel.h
#import <Foundation/Foundation.h>
@interface TestModel : NSObject
// 数据
@property(nonatomic,copy)NSString *name;
@end
// - TestView.h
1 #import <UIKit/UIKit.h>
2 @class TestViewModel;
3
4 // 声明协议:同 VM 交互
5 @protocol TestViewDelegate <NSObject>
6
7 -(void)doSomethings;
8
9 @end
10
11 @interface TestView : UIView
12
13 // 控件
14 @property(nonatomic,strong)UILabel *TVLabel;
15 // 拥有 VM
16 @property(nonatomic,weak)TestViewModel *viewModel;
17 // 代理
18 @property(nonatomic,weak)id<TestViewDelegate>delegate;
19
20 @end
// - TestView.m
1 #import "TestView.h"
2 #import "NSObject+FBKVOController.h"
3 @implementation TestView
4
5 // 创建控件
6 - (instancetype)initWithFrame:(CGRect)frame{
7 self = [super initWithFrame:frame];
8 if (self) {
9
10 self.TVLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 40, self.frame.size.width -40, self.frame.size.height -80)];
11 self.TVLabel.backgroundColor = [UIColor orangeColor];
12 self.TVLabel.textAlignment = NSTextAlignmentCenter;
13 [self addSubview:self.TVLabel];
14
15 }
16 return self;
17 }
18
19 // 代理
20 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
21
22 if ([self.delegate respondsToSelector:@selector(doSomethings)]) {
23 [self.delegate doSomethings];
24 }
25 }
26
27
28 // 重写 setter 方法,监听来自 viewModel 的数据
29 - (void)setViewModel:(TestViewModel *)viewModel{
30
31 // 获取数据
32 _viewModel = viewModel;
33
34 // 实现监听
35 // 因为 RAC(比较庞大,好多公司使用 MVVM+RAC 两者搭配)、iOS 自带的 KVO(太过零碎,使用起来也并不友好)
36
37 // 这里我们采用 Facebook 封装的 KVOController
38 // 链接 https://github.com/facebookarchive/KVOController
39
40 __weak typeof(self) weakSelf = self;
41 [self.KVOController observe:viewModel keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
42
43 // UI 赋值
44 weakSelf.TVLabel.text = change[NSKeyValueChangeNewKey];
45
46 }];
47 }
48
49 @end
// - TestViewModel.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TestViewModel : NSObject
// 入口方法:参数是 controller
-(instancetype)initWithController:(UIViewController *)controller;
@end
// - TestViewModel.m
1 #import "TestViewModel.h"
2 #import "TestView.h"
3 #import "TestModel.h"
4 @interface TestViewModel()<TestViewDelegate,UITextFieldDelegate> // 接受来自视图的协议
5
6 // Controller
7 @property(nonatomic,weak)UIViewController *controller;
8
9 // Model
10 @property(nonatomic,strong)TestModel *tModel;
11 // 来自 M 的数据。M 和 VM 两者绑定需要
12 @property(nonatomic,copy)NSString *name;
13
14 @end
15
16 @implementation TestViewModel
17
18 // 入口:绑定一个 Controller 并出业务逻辑
19 -(instancetype)initWithController:(UIViewController *)controller{
20
21 if (self = [super init]) {
22
23 // 绑定控制器
24 self.controller = controller;
25
26 // 视图
27 TestView *tView = [[TestView alloc] initWithFrame:CGRectMake(30, 80, SCREEN_WIDTH - 60, 200)];
28 tView.backgroundColor = [UIColor redColor];
29 [self.controller.view addSubview:tView];
30 tView.delegate = self;
31 // 视图拥有了 viewModel
32 tView.viewModel = self;
33
34 // 加载数据模
35 _tModel = [TestModel new];
36 _tModel.name = @"QQ";
37
38 // 注意和 MVP 不同是:M 和 VM 两者需要绑定
39 // 核心思想就是 V、M 能够有拥有 VM,但 V、M 两者独立,互不影响
40 self.name = _tModel.name; // 把 Model 的数据绑定到 VM 中
41 // 通过 V 和 VM 两者的数据绑定,并且 V 对 VM 进行了监听,那么数据一旦发生改变,V 就能捕获
42
43 // 验证监听:动态检测文本内容
44 UITextField *testTF = [[UITextField alloc] initWithFrame:CGRectMake(50, 500, controller.view.frame.size.width - 100, 50)];
45 testTF.clearButtonMode = UITextFieldViewModeWhileEditing;
46 testTF.textColor = [UIColor whiteColor];
47 testTF.placeholder = @"点我更改 QQ";
48 testTF.backgroundColor = [UIColor redColor];
49 [controller.view addSubview:testTF];
50 testTF.delegate = self;
51
52 }
53
54 return self;
55
56 }
57
58 // 动态文本
59 - (void)textFieldDidChange:(id)sender {
60
61 UITextField *field = (UITextField *)sender;
62 // 变更数据:数据往往要从 Model 中获取,这里仅作验证
63 self.name = field.text;
64 }
65
66 #pragma mark - <UITextFieldDelegate>
67 // 动态检测文本框内容
68 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
69
70 [textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
71 return YES;
72 }
73
74 #pragma mark - <TestViewDelegate>
75 // 代理
76 - (void)doSomethings{
77
78 // 随机色
79 self.controller.view.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0
80 green:arc4random()%255/255.0
81 blue:arc4random()%255/255.0
82 alpha:1.0];
83 }
84
85 @end
// - ViewController.m
1 #import "ViewController.h"
2 #import "TestViewModel.h"
3 @interface ViewController()
4
5 // ViewModel
6 @property(nonatomic,strong)TestViewModel *viewModel;
7 @end
8
9 @implementation ViewController
10
11 - (void)viewDidLoad {
12 [super viewDidLoad];
13 self.view.backgroundColor = [UIColor cyanColor];
14
15 // 绑定 TestViewModel
16 self.viewModel = [[TestViewModel alloc] initWithController:self];
17
18 }
19
20 @end
运行效果