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
运行效果