ReactiveCocoa 框架,在刚听说过这个框架时,我便在github上搜索了一下,star的数量确实让我震惊了一下。查询资料了解后,感觉自己入行没多久,感觉这个框架高深莫测,不敢涉猎。
这个周末的闲暇之余,我决定学习一下这个框架并写个登录demo。
开始了。
首先来导入框架,我使用的是Cocopods来导入框架。毕竟这个框架手动导入起来实在是太麻烦
通过stroyboard搭建了一个登录界面,并声明了三个变量:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userTF;
@property (weak, nonatomic) IBOutlet UITextField *passTF;
@property (weak, nonatomic) IBOutlet UIButton *loginBTN;
@property (nonatomic, strong) LoginService *service; //这是自己写的模拟后台
@end
导入框架后,在view controller中引入ReactiveCocoa的头文件
#import
在viewdidload中写上代码:
RACSignal *userTFSingal = self.userTF.rac_textSignal;
RACSignal *passTFSingal = self.passTF.rac_textSignal;
[userTFSingal subscribeNext:^(id x) {
NSLog(@"打印出来的用户名文本:%@",x);
}];
运行之后会发现 userTF中值发生了改变就会打印一次userTF中的text;
也可以通过fliter来设定subscribeNext响应条件。
[[userTFSingal filter:^BOOL(NSString *text) {
return text.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"用户名超过三位的文本:%@",x);
}];
map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。在上面的代码中,map以NSString为输入,取字符串的长度,返回一个NSNumber。
[[[userTFSingal map:^id(NSString *text) {
return @(text.length);
}]
filter:^BOOL(NSNumber *length) {
return [length intValue]>5;
}]
subscribeNext:^(id x) {
NSLog(@"用户名文本长度:%@",x);
}];
上面说明了ReactiveCocoa的UITextField部分使用。下面开始实现登录逻辑吧!
设置有效文本长度
- (BOOL)isValidUsername:(NSString *)username {
return username.length > 3;
}
- (BOOL)isValidPassword:(NSString *)password {
return password.length > 3;
}
创建有效状态信号
RACSignal *validUsernameSignal = [self.userTF.rac_textSignal
map:^id(NSString *text) {
return @([self isValidUsername:text]);
}];
RACSignal *validPasswordSignal = [self.passTF.rac_textSignal
map:^id(NSString *text) {
return @([self isValidPassword:text]);
}];
根据文本是否有效改变文本框颜色
[[validPasswordSignal map:^id(NSNumber *passValid) {
return [passValid boolValue] ? [UIColor clearColor] : [UIColor grayColor];
}]
subscribeNext:^(UIColor *color) {
self.passTF.backgroundColor = color;
}];
相对于上面这个方法更推荐使用下面这个方法:
RAC(self.userTF, backgroundColor) = [validUsernameSignal map:^id(NSNumber *userValid) {
return [userValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
}];
RAC(self.passTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *passValid) {
return [passValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
}];
聚合信号
combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。
RACSignal *loginSignal = [RACSignal combineLatest:@[validPasswordSignal,validUsernameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
这里是模拟后台
LoginService接口
typedef void (^RWSignInResponse)(BOOL);
@interface LoginService : NSObject
- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;
@end
实现
- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock {
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"];
completeBlock(success);
});
}
创建登录信号
- (RACSignal *)loginInSignal {
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
[self.service signInWithUsername:self.userTF.text password:self.passTF.text complete:^(BOOL success) {
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}];
return nil;
}];
}
UIButton
设置按钮状态
[loginSignal subscribeNext:^(NSNumber *login) {
self.loginBTN.enabled = [login boolValue];
}];
UIButton部分使用
[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"登录按钮被点击了");
}];
输出登录结果
[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
return [self loginInSignal];
}] subscribeNext:^(id x) {
NSLog(@"登录结果:%@",x);
}];
这里你会发现打印的登录结果 不是bool类型
当点击按钮时,rac_signalForControlEvents发送了一个next事件(事件的data是UIButton)。map操作创建并返回了登录信号,这意味着后续步骤都会收到一个RACSignal。这就是你在subscribeNext:这步看到的。
上面问题的解决方法,有时候叫做信号中的信号,换句话说就是一个外部信号里面还有一个内部信号。你可以在外部信号的subscribeNext:block里订阅内部信号。不过这样嵌套太混乱啦,还好ReactiveCocoa已经解决了这个问题。
信号中的信号
解决的方法很简单,只需要把map操作改成flattenMap就可以了:
[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^id(id value) {
return [self loginInSignal];
}] subscribeNext:^(NSNumber *loginIn) {
NSLog(@"登录结果:%@",loginIn);
BOOL success = [loginIn boolValue];
if (success) {
[self performSegueWithIdentifier:@"Kitten" sender:self];
}
}];
为了防止多次点击Button,使用doNext添加附加操作。
[[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
self.loginBTN.enabled = NO;
}] flattenMap:^RACStream *(id value) {
return [self loginInSignal];
}] subscribeNext:^(NSNumber *loginIn) {
NSLog(@"登录结果:%@",loginIn);
BOOL success = [loginIn boolValue];
if (success) {
[self performSegueWithIdentifier:@"Kitten" sender:self];
}
}];
好了 登录demo完成了。
参考文章:
最快让你上手ReactiveCocoa之基础篇
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ==>此文的初始工程
以及译文