1. MVVM简介 不过多赘述MVC,用最通俗的方式解说MVVM。
拆解:
M: Model
,包括数据模型、访问数据库的操作和网络请求等
V: View
,包括了iOS中的 View
和 controller
组成,负责 UI 的展示,绑定 viewModel 中的属性
VM: ViewModel
,负责从 Model
中获取 View
所需的数据,转换成 View
可以展示的数据,并暴露公开的属性和命令供 View
进行绑定
Binder: 这是我最近发现的,在标准MVVM中没有提到的一部分,但是如果使用MVVM + ReactiveCocoa就会自然地写出这一层。这一层主要为了实现响应式编程的功能,实现 View
和 ViewModel
的同步
MVC——>MVVM:
MVC是苹果官方推荐的开发模式,但是伴随这这模式产生的问题非常的多,这是随着项目的逐渐扩大、架构的逐渐复杂显示出来的,这也是为什么MVC也被调侃成Massive View Controller(重量级视图控制器) 。大多数情况下,小型项目MVC开发不会带来太大的负担,即使你将大量的逻辑代码(不包括通用的工具类逻辑)放在了ViewController中,但只要该部分由一个人维护,相对来说还是可以保持逻辑清晰的。
但当项目越来越大时,或者一个模块会有多个人维护时,读代码变成了一件非常困难的事,并且,MVC模式的iOS开发一直存在难以测试 的问题。博主在做JAVA开发时JUnit的测试就像每天的必修课一样。开始iOS开发后,加上第二家公司一直没有QA,线上发现的BUG简直就是每天的噩梦。MVVM带来的好处是 VM
层可以方便的做测试 ,因为 VM
层是独立的逻辑 ,脱离对 View
和 Model
的依赖。
少写字,多写代码,赶紧进入下一部分介绍,尽快去了解如何编码。
2. ReactiveCocoa 2.1 简介
简介: ReactiveCocoa简称RAC,集合了函数式编程 和响应式编程 ,这也是为什么ReactiveCocoa被描述为函数响应式编程(FRP)框架。
ReactiveCocoa解决的问题:iOS开发中有多种事件处理方式,相信你一定也曾想过这些坑爹的地方,通常有这些事件处理方式:Action、delegate、Notification、KVO。并且通常这些代码总是散落在代码的各个角落,几度分散。ReactiveCocoa为事件提供了很多处理方法,可以把要处理的事情,和监听的事情的代码放在一起,非常方便管理。
关于ReactiveCocoa的基本用法,希望你能认真的阅读这篇博文
2.2 常用宏
RAC(TARGET, [KEYPATH, [NIL_VALUE]]):
用于给某个对象的某个属性绑定。
1 2 RAC(self .labelView,text) = _textField.rac_textSignal;
**RACObserve(self, name):**
监听某个对象的某个属性,返回的是信号,可以用来代替KVO
1 2 3 [RACObserve(self .view, center) subscribeNext:^(id x) { NSLog (@"%@" ,x); }];
3. 实践 3.1 实现内容 做一个简单的登陆功能,两个输入框,一个登陆按钮。 简单的用户名密码验证,要求都在6位数以上即可,不符合要求时禁用登陆按钮。
3.2 Coding 界面大概是这样的感觉,简单的拉一个即可:
M层 抽出简单的User模型,Thin Model,不包含功能型方法:
1 2 3 4 5 6 7 #import <Foundation/Foundation.h> @interface User : NSObject @property (nonatomic , copy ) NSString *username;@property (nonatomic , copy ) NSString *password;@end
VM层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #import <Foundation/Foundation.h> #import "ReactiveCocoa.h" @interface LoginViewModel : NSObject @property (nonatomic , strong ) NSString *userName;@property (nonatomic , strong ) NSString *password;@property (nonatomic , strong ) RACSubject *successSubject;@property (nonatomic , strong ) RACSubject *failureSubject;@property (nonatomic , strong ) RACSubject *errorSubject;- (RACSignal *)validSignal; - (void )login; @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #import "LoginViewModel.h" #import "User.h" @interface LoginViewModel ()@property (nonatomic , strong ) RACSignal *userNameSignal;@property (nonatomic , strong ) RACSignal *passwordSignal;@property (nonatomic , strong ) NSArray *requestData;@end @implementation LoginViewModel - (instancetype )init { if (self = [super init]) { _userNameSignal = RACObserve(self , userName); _passwordSignal = RACObserve(self , password); _successSubject = [RACSubject subject]; _failureSubject = [RACSubject subject]; _errorSubject = [RACSubject subject]; } return self ; } - (RACSignal *)validSignal { RACSignal *validSignal = [RACSignal combineLatest:@[_userNameSignal, _passwordSignal] reduce:^id (NSString *userName, NSString *password){ return @(userName.length >= 6 && password.length >= 6 ); }]; return validSignal; } - (void )login{ User *user = [[User alloc] init]; user.username = self .userName; user.password = self .password; _requestData = @[user]; [_successSubject sendNext:_requestData]; } @end
通过这一层,你是否发现,VM是可以单独测试的,也是可以单独编写的,即使你没有写 View
层,你可以编写针对VM的Unit进行功能测试,确保VM无误后继续编写后续代码。
V层 首先我们要通过RAC实现一部分UI的功能——输入文字的时候同步将文字保存起来&&控制按钮的禁用状态。
想想看我们通常是怎么做的?
通过实现UITextField的代理
在 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
方法中获取输入的文字,赋值给username属性和password属性
再判断username和password是否符合要求
再设置按钮的enabled属性
是不是看一看就觉得乱糟糟的,按钮的addTarget在一个地方,代理又在一个地方,再加上判断用户名密码合法逻辑单独抽出的方法。OMG。
ReactiveCocoa是怎么做的?
1 2 3 RAC(self .viewModel, userName) = self .tfUserName.rac_textSignal; RAC(self .viewModel, password) = self .tfPassword.rac_textSignal; RAC(self .btLogin, enabled) = [self .viewModel validSignal];
三句话,清清爽爽。
再加上 ViewModel
的信号绑定,将上面的代码放到一个方法中,命名为bindModel
最后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #import "ViewController.h" #import "ReactiveCocoa.h" #import "LoginViewModel.h" #import "User.h" @interface ViewController ()@property (nonatomic , strong ) LoginViewModel *viewModel;@property (weak , nonatomic ) IBOutlet UITextField *tfUserName;@property (weak , nonatomic ) IBOutlet UITextField *tfPassword;@property (weak , nonatomic ) IBOutlet UIButton *btLogin;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self bindModel]; } - (void )bindModel { self .viewModel = [[LoginViewModel alloc] init]; RAC(self .viewModel, userName) = self .tfUserName.rac_textSignal; RAC(self .viewModel, password) = self .tfPassword.rac_textSignal; RAC(self .btLogin, enabled) = [self .viewModel validSignal]; [self .viewModel.successSubject subscribeNext:^(NSArray * x) { User *user = x[0 ]; NSLog (@"username:%@\tpassword:%@" , user.username, user.password); NSLog (@"登陆成功" ); }]; [self .viewModel.failureSubject subscribeNext:^(id x) { NSLog (@"登陆失败" ); }]; [self .viewModel.errorSubject subscribeNext:^(id x) { NSLog (@"登陆错误" ); }]; [[self .btLogin rac_signalForControlEvents:UIControlEventTouchUpInside ] subscribeNext:^(id x) { [self .viewModel login]; }]; } @end
这样,ViewController中事件处理的所有代码被集中在一起,方便管理,你的代码变得如此清爽、低耦合。
代码 以上代码地址请点这里 。
补充 小鱼最近在换工作哟~各路的朋友有推荐的请务必介绍我哟~ 简历在这里
有什么问题都可以在博文后面留言,或者微博上私信我。
博主是 iOS 妹子一枚。
希望大家一起进步。
我的微博:Lotty周小鱼