iOS基础 - 架构模式:MVVM

▶ 什么是 MVVM

Model-View-ViewModel 是 M-V-VM 三部分组成,它本质上是 MVC 的改进版

MVVM 就是将其中 V层 的状态和行为抽象化,其中 ViewModel 将 视图UI 和 业务逻辑 分开,它取出 M层 数据的同时也可以帮忙处理 V层 中由于需要展示内容而涉及的业务逻辑

MVVM 采用双向数据绑定。V层 中数据变化将自动反映到 VM 上;同样 M层 中数据变化也将会自动展示在界面上。MVVM 的核心思想就是关注 M层 的变化,让 MVVM 框架利用自己的机制自动更新 DOM,也就是所谓的数据-视图分离

MVVM 的缺点/优点

A. 好处

代码清晰:ViewModel 分离出来大部分的 Controller 代码,更加清晰和容易维护

方便测试:大部分 Bug 来自于逻辑处理,由于 ViewModel 把逻辑分离出来,可对 ViewModel 构造单元测试

开发解耦:一位开发者负责逻辑实现,另一位开发者负责 UI 实现

B. 坏处

代码量比 MVC 多、需对每个 Controller 实现绑定,这是分离不可避免的工作量

▶ MVVM 实战

同 MVP 架构模式一样,我们需要把 VM 和 C 两者绑定在一起,目录结构如下

具体实现如下

// - 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:VM层

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TestViewModel : NSObject

// 入口方法
-(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

运行效果

 

posted on 2022-05-20 16:21  低头捡石頭  阅读(2009)  评论(0编辑  收藏  举报

导航