欢迎访问我的个人blog
https://ekulelu.github.io/
上一篇文章我们使用ReactiveCocoa搭建了一个简单的登录页面,这一次我们来搭建注册页面。
Account和password的有效规制还是和之前一样,长度必须大于5,否则输入框背景色是红色的,这一步直接用之前的代码就可以了。
注册按钮的响应和登录的响应应该是一样的逻辑,对于之前的伪代码来讲,也几乎没什么改动。
但是注册和登录最起码有个不同的东西,就是注册的用户名可能已经被占用,我们想用户在输入完了用户名之后,从后台对用户名是否可用进行判断,如果可用,才允许用户点击“注册”按钮。
仿照登录时候将登录方法封装成为信号的做法,或许可以把用户名检查的功能也封装成为一个信号,但我们这里尝试另外一种做法:使用RACSubject对象。RACSubject对象继承于RACSignal,相比于RACSignal,RACSubject具有主动发送信号值的功能。下面新建一个RACSubject对象。
RACSubject *isAccountAvailableSubject = [RACSubject subject];
这个信号代表了用户名是否可以用,且这个信号的值可以由代码主动去控制发出。注册按钮应该是用户名合法且可用,且密码合法的情况下才可以点击,所以把这3个信号combine一起
RAC(self.registerBtn, enabled) = [RACSignal combineLatest:@[isAccountAvailableSubject, accountValidSignal,passwordVaildSignal]
reduce:^id(NSNumber *accountUse, NSNumber* accountValid, NSNumber* passwordVaild){
return @(accountUse.intValue == AccountStatusAvailable && accountValid.boolValue && passwordVaild.boolValue);
}];
我们在用户名框的旁边放置一个imageView,用它的背景颜色来表示这个用户名是否可用,绿色代表可用,蓝色代表正在查找,黄色代表不可用。我们可以将这个属性和刚刚创建的isAccountAvailableSubject联系起来。
RAC(self.accountAvailableImgView, backgroundColor) = [isAccountAvailableSubject
map:^id(NSNumber *value) {
switch (value.intValue) {
case AccountStatusAvailable:
return [UIColor greenColor];
break;
case AccountStatusChecking:
return [UIColor blueColor];
break;
case AccountStatusUnavailable:
default:
return [UIColor yellowColor];
break;
}
}];
其中有一些枚举定义如下:
typedef NS_ENUM(int, AccountStatus) {
AccountStatusAvailable = 0,
AccountStatusChecking = 1,
AccountStatusUnavailable = 2,
};
如果用户名不合法,判断用户名是否可用的imageView应该不要显示出来
RAC(self.accountAvailableImgView, hidden) = [accountValidSignal map:^id(NSNumber *value) {
return @(!value.boolValue);
}];
剩下就是根据用户名输入去查询用户名是否合法了。这个方法我们已经写好,问题就在于它的调用时间,它应该是在用户名输入合法的时候调用,但如果去订阅accountValidSignal,那么在切换输入框的时候也会调用。所以不能直接订阅,这里再介绍一个方法
- (RACSignal *)sample:(RACSignal *)sampler;
[SingleA sample:SingleB]; 这个意思是只有当SingleB有信号的时候才会去获取SingleA最新值。我们可以把SingleA设为accountValidSignal,SingleB设为[self.accountTV.rac_textSignal distinctUntilChanged],
distinctUntilChanged表示信号的值比上一次的信号值有更新了才会发送。
如果不加distinctUntilChanged,那么焦点离开也会发出rac_textSignal
这样就会在只有用户名改变的时候才会去获取accountValidSignal的最新值,然后加上filter进一步对用户名是否合法再过滤就可以了。
[[[accountValidSignal sample:[self.accountTV.rac_textSignal distinctUntilChanged]]
filter:^BOOL(NSNumber *value) {
return value.boolValue;
}]
subscribeNext:^(id x) {
[isAccountAvailableSubject sendNext:@(AccountStatusChecking)];
[self checkAccount:self.accountTV.text complete:^(Boolean success) {
if (success) {
[isAccountAvailableSubject sendNext:@(AccountStatusAvailable)];
NSLog(@"can use accout");
} else {
[isAccountAvailableSubject sendNext:@(AccountStatusUnavailable)];
NSLog(@"can not use accout");
}
}];
}];
上面方法里面在回调里面使用isAccountAvailableSubject发送检查结果,accountAvailableImgView和注册按钮就会根据检查结果进行更新。
至此,一个简单的注册框就完成了。
当然这里面还有问题:假如第一次输入了一个合法的用户名,然后发起用户名检查,这时候立刻加上一个字符,那么又会发起第二次用户名检查,这个时候恰好第一次用户名检查的结果回来了,显示用户名可用,注册按钮可以点击。但是输入框里面已经是新的用户名了。这个问题留给大家思考。
循环引用
这个问题老生常谈了,有了block,就要小心这个问题。RAC采用了大量的block,一不小心就循环引用了。
为此,RAC里面提供了一个十分好用的宏来声明self的弱引用。这个宏定义在RACmetamacros.h里面。
这对宏是
@weakify(self)
@strongify(self)
使用的方法如下,在block外边使用@weakify(self),然后在block里面使用@strongify(self)。以后block里面的self就是弱引用的了。
@weakify(self)
RACSignal *accountValidSignal = [self.accountTV.rac_textSignal map:^id(id value) {
@strongify(self)
return @(self.accountTV.text.length > 5);
}];
具体原理是weakify(self)声明了一个self的弱引用变量,strongify(self)把这个弱引用变量变为了局部的强引用变量。至于这个@,其实是autoreleasepool {} (在debug编译下) 或者 try {} @catch (…) {} (在release编译下)