项目中学习ReactiveCocoa的使用方法
一、注册控制器
控制器上的一个属性
@property (weak, nonatomic) IBOutlet UIBarButtonItem *signInBtn;
在
viewDidLoad
方法中写入
self.signInBtn.rac_command = self.viewModel.executeSignInCommand;
self.viewModel是控制器上的一个viewModel
@property (nonatomic, weak) MSFAuthorizeViewModel *viewModel;
其上有个属性
@property (nonatomic, strong) RACCommand *executeSignInCommand;
这个executeSignInCommand在viewModel的初始化方法中生成
- (instancetype)initWithServices:(id <MSFViewModelServices>)services { self = [super init]; if (!self) { return nil; } _services = services; ..... _executeSignInCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { self.loginType = MSFLoginSignIn; [self.services presentViewModel:self]; return [RACSignal return:nil]; }]; .... }
而presentViewModel:仅仅只是一个协议,没有实现,有个样本
- (void)presentViewModel:(id)viewModel { id viewController; if ([viewModel isKindOfClass:MSFAuthorizeViewModel.class]) { MSFAuthenticateViewController *loginViewController = [[MSFAuthenticateViewController alloc] initWithViewModel:viewModel]; viewController = [[UINavigationController alloc] initWithRootViewController:loginViewController]; UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(UIScreen.mainScreen.bounds), 20)]; view.backgroundColor = UIColor.blackColor; [[(UIViewController *)viewController view] addSubview:view]; } else { NSLog(@"an unknown ViewModel was present!"); } [self.navigationController presentViewController:viewController animated:YES completion:nil]; }
再看看控制器的其他属性
@property (nonatomic, weak) IBOutlet UITextField *usernameF; @property (nonatomic, weak) IBOutlet UITextField *captchaF; @property (nonatomic, weak) IBOutlet UITextField *passwordF;
也是在viewDidLoad中处理的,方法viewWillAppear有一执行就会有一个信号发送,控制器订阅了这个信号,并在收到信号后,执行操作:
把控件上的文件赋值给viewModel上的对应的属性。
@weakify(self) [[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) { @strongify(self) self.viewModel.username = self.usernameF.text; self.viewModel.password = self.passwordF.text; self.viewModel.loginType = MSFLoginSignUp; }];
当然,相应的textField文本改变时,我们也会处理
输入的文本长度超过规定的长度,则进行截取然后放置到field中,再赋值给viewModel中对应的属性
[self.username.rac_textSignal subscribeNext:^(id x) { @strongify(self) if ([x length] > MSFAuthorizeUsernameMaxLength) self.username.text = [x substringToIndex:MSFAuthorizeUsernameMaxLength]; self.viewModel.username = self.username.text; }];
密码输入框也是
[self.password.rac_textSignal subscribeNext:^(id x) { @strongify(self) if ([x length] > MSFAuthorizePasswordMaxLength) self.password.text = [x substringToIndex:MSFAuthorizePasswordMaxLength]; self.viewModel.password = self.password.text; }];
还有密码field在按下下一步的return键时,就相当于按下了注册按钮,此时就会调用viewModel上的executeSignUp命令
[self.password.rac_keyboardReturnSignal subscribeNext:^(id x) { @strongify(self) [self.viewModel.executeSignUp execute:nil]; }];
这个命令也是跟登录命令一样在同一个方法中初始化
_executeSignUp = [[RACCommand alloc] initWithEnabled:self.signUpValidSignal signalBlock:^RACSignal *(id input) { @strongify(self) return [self executeSignUpSignal]; }];
当然,这个命令要受到能点不能点击的影响self.signUpValidSignal,当用户名和密码还有验证码都有输入的情况下,就会调用自身上的一个excuteSignUpSignal
- (RACSignal *)signUpValidSignal { return [RACSignal combineLatest:@[ RACObserve(self, username), RACObserve(self, password), RACObserve(self, captcha), ] reduce:^id(NSString *username, NSString *password, NSString *captcha){ return @(username != nil && password != nil && captcha != nil); }]; }
突然间感觉这里好复杂不再深究,先看其他的
- (RACSignal *)executeSignUpSignal { if (![self.username isMobile]) { return [RACSignal error:[self.class errorWithFailureReason:@"请填写真实的手机号码"]]; } else if (![self.password isPassword]) { return [RACSignal error:[self.class errorWithFailureReason:@"请填写8到16位数字和字母组合的密码"]]; } else if (![self.captcha isCaptcha]) { return [RACSignal error:[self.class errorWithFailureReason:@"请填写验证码"]]; } else if (!self.agreeOnLicense) { return [RACSignal error:[self.class errorWithFailureReason:@"请阅读注册协议"]]; } MSFUser *user = [MSFUser userWithServer:MSFServer.dotComServer]; return [[MSFClient signUpAsUser:user password:self.password phone:self.username captcha:self.captcha] doNext:^(MSFClient *client) { _signInValid = YES; [self.services setHttpClient:client]; [[client fetchUserInfo] subscribeNext:^(MSFUser *x) { [client.user mergeValueForKey:@keypath(x.personal) fromModel:x]; [client.user mergeValueForKey:@keypath(x.professional) fromModel:x]; [client.user mergeValueForKey:@keypath(x.contacts) fromModel:x]; [client.user mergeValueForKey:@keypath(x.profiles) fromModel:x]; [client.user mergeValueForKey:@keypath(x.insurance) fromModel:x]; }]; }]; }
有个显示密码与隐藏密码的按钮
[[self.showPasswordButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { @strongify(self) self.showPasswordButton.selected = !self.showPasswordButton.selected; NSString *text = self.password.text; self.password.text = text; self.password.enabled = NO; [self.password setSecureTextEntry:!self.showPasswordButton.selected]; self.password.enabled = YES; [self.password becomeFirstResponder]; }];
再看看验证码输入框,长度限制、截取、同步viewModel的对应属性
[self.captcha.rac_textSignal subscribeNext:^(id x) { @strongify(self) if ([x length] > MSFAuthorizeCaptchaMaxLength) self.captcha.text = [x substringToIndex:MSFAuthorizeCaptchaMaxLength]; self.viewModel.captcha = self.captcha.text; }];
同步viewModel上的命令
self.sendCaptchaButton.rac_command = self.viewModel.executeCaptcha;
执行命令时的操作,键盘释放,SVProgressHUD显示正在获取验证码
[self.sendCaptchaButton.rac_command.executionSignals subscribeNext:^(RACSignal *captchaSignal) { @strongify(self) [self.view endEditing:YES]; [SVProgressHUD showWithStatus:@"正在获取验证码" maskType:SVProgressHUDMaskTypeClear]; [captchaSignal subscribeNext:^(id x) { [SVProgressHUD dismiss]; }]; }];
当这个命令有错误时的回调
[self.sendCaptchaButton.rac_command.errors subscribeNext:^(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.userInfo[NSLocalizedFailureReasonErrorKey]];
}];
同理,提交、注册按钮也是类似 的,先绑定命令
self.commitButton.rac_command = self.viewModel.executeSignUp;
然后,执行命令的回调,释放键盘,提示正在注册...然后会发送一个通知,没看到这个是什么意思 [signUpSignal subscribeNext:^(id x) {
[self.commitButton.rac_command.executionSignals subscribeNext:^(RACSignal *signUpSignal) { @strongify(self) [self.view endEditing:YES]; [SVProgressHUD showWithStatus:@"正在注册..." maskType:SVProgressHUDMaskTypeClear]; [signUpSignal subscribeNext:^(id x) { [[NSNotificationCenter defaultCenter] postNotificationName:@"MSFREQUESTCONTRACTSNOTIFACATION" object:nil]; [SVProgressHUD dismiss]; }]; }];
收到错误时的信号
[self.commitButton.rac_command.errors subscribeNext:^(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.userInfo[NSLocalizedFailureReasonErrorKey]];
}];
验证码倒计时Label
@property (nonatomic, weak) IBOutlet UILabel *counterLabel;
viewDidLoad中操作,viewModel上的对应的属性变更时,会同步到这个控制器上的label来
RAC(self, counterLabel.text) = RACObserve(self, viewModel.counter);