文字说完了,如果有些小伙伴还不太明白,那看下面这张原理图吧。把三种信号量我们可以类比成十字路口的红绿灯。successObject就是绿灯,可以走正常流程。failureObject是黄灯,先等一下,完成该做的就可以走绿灯了。而errorObject就是一红灯,报错异常,终止业务流程并提升错误信息。有图有真相,到这儿如果还不理解我就没招了。
在Public方法中- (id) buttonIsValid; 负责返回登录按钮是否可用的信号量。- (void)login;发起网络请求,调用登录网络接口。
(2)代码的具体实现如下(VCViewModel.m中的代码),私有属性如下。userNameSignal用来存储用户名的信号量,passwordSignal是用来存储密码的信号量。reqestData则是用来存储返回数据的。
1 @interface VCViewModel () 2 @property (nonatomic, strong) RACSignal *userNameSignal; 3 @property (nonatomic, strong) RACSignal *passwordSignal; 4 @property (nonatomic, strong) NSArray *requestData; 5 @end
(3)VCViewModel的初始化方法如下,负责初始化属性。
1 - (instancetype)init 2 { 3 self = [super init]; 4 if (self) { 5 [self initialize]; 6 } 7 return self; 8 } 9 10 - (void)initialize { 11 _userNameSignal = RACObserve(self, userName); 12 _passwordSignal = RACObserve(self, password); 13 _successObject = [RACSubject subject]; 14 _failureObject = [RACSubject subject]; 15 _errorObject = [RACSubject subject]; 16 }
(4) 发送登录按钮是否可用信号的方法如下,主要用到了信号量的合并。
//合并两个输入框信号,并返回按钮bool类型的值 - (id) buttonIsValid { RACSignal *isValid = [RACSignal combineLatest:@[_userNameSignal, _passwordSignal] reduce:^id(NSString *userName, NSString *password){ return @(userName.length >= 3 && password.length >= 3); }]; return isValid; }
(5) 模拟网络请求的发送,并发出网络请求成功的信号,具体代码如下
1 - (void)login{ _requestData = @[_userName, _password]; [_successObject sendNext:_requestData]; }
4. 上面是VM的实现,如果要进行单元测试的话,就对相应的VM类进行初始化,调用相应的函数进行单元测试即可。接着就是看如何在相应的VC模块中使用VM。
(1) 在VC中实例化相应的VM类,并绑定相应的参数和实现接收不同信号的方法,具体代码如下:
- (void)bindModel { 3 _viewModel = [[VCViewModel alloc] init]; RAC(self.viewModel, userName) = self.userNameTextField.rac_textSignal; 7 RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal; 8 RAC(self.loginButton, enabled) = [_viewModel buttonIsValid]; 9 10 @weakify(self); [self.viewModel.successObject subscribeNext:^(NSArray * x) { 14 @strongify(self); bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:]; 16 vc.userName = x[0]; 17 vc.password = x[1]; 18 [self presentViewController:vc animated:YES completion:^{ 19 20 }]; 21 }]; [self.viewModel.failureObject subscribeNext:^(id x) { 25 26 }]; [self.viewModel.errorObject subscribeNext:^(id x) { 30 31 }]; 32 33 }
(2) 点击登录按钮,调用VM中登录相应的网络请求方法即可
1 - (void)onClick { [[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] 4 subscribeNext:^(id x) { 5 [_viewModel login]; 6 }]; 7 }
到此为止,一个完整模拟登录模块的RAC下的MVVM就实现完毕。当然上面的Demo是非常简陋的,还有好多地方需要进化。不过麻雀虽小,道理你懂得。主要是通过上面的Demo来感受一下RAC中的信号量机制以及应用场景。
5.上面代码写完,我们就可以运行看一下运行效果了,下方是运行后的效果,
上述工程GitHub分享链接:https://github.com/lizelu/MVVMWithReactiveCocoa
其他参考资料:
https://github.com/ReactiveCocoa/ReactiveViewModel
http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/
http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/
http://nshipster.cn/reactivecocoa/
http://limboy.me/ios/2013/06/19/frp-reactivecocoa.html
https://vimeo.com/65637501
http://southpeak.github.io/blog/2014/08/08/mvvmzhi-nan-yi-:flickrsou-suo-shi-li/
http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-%5B%3F%5D-:xin-hao/
http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-er-:twittersou-suo-shi-li/
ViewModel:
Kicking off network or database requests
Determining when information should be hidden or shown
Date and number formatting
Localization
ViewController:
Layout
Animations
Device rotation
View and window transitions
Presenting loaded UI