最近听说一个新词:响应式编程,进而在iOS中了解了到ReactiveCocoa,这是我学习的教程链接:ReactiveCocoa入门教程,以下是我通过学习自己的感悟和理解,不当之处请评论中指出,互相学习,及时修改,so...废话少说:
先来下准备工作:CocoaPods下载ReactiveCocoa库,刚开始下了个最新的全是swift,看了下介绍才发现3.0和之后就全改成swift版本了,如图
So:
platform :ios, '8.0'
use_frameworks!
pod 'ReactiveCocoa', '~> 2.5'
终于能开始了:
这是教程的原图,登录操作,看到这我自己的常规逻辑操作肯定是给两个UITextField加valueChanged事件监测内容改变继而通过内容改变修改登录按钮的操作以及可能的各种需求(改变颜色balabala...),给按钮加点击在点击方法里写判断跳转等。
教程上大致也是这个逻辑,不同之处在于我们之前写的事件常规的都是一个方法,而这里通过ReactiveCocoa则是分装在block内。
说个题外话,既然效果一样都新写方法好啦,当然可以实现,但我之前有一次面试一个试题问我block的优点,面试官告诉我block的优点就是可以方便的调用方法内的局部变量,不需要生成全局变量,属性等,好吧,我感受到了。
打开初始的UIViewController,引入头文件
#import
.h
先来小试牛刀:
[self.usernameTextField.rac_textSignal subscribeNext:^(id x){
NSLog(@"%@",x);
}];
你会惊奇发现:每次textFied内容改变就会打印一次,比添加target-action方法简单多了。做到这步的时候我想为啥丫监测的是输入的text而不是颜色,其他什么,看这个属性rac_textSignal,翻译下:这个库_文本信号(好像是这样的)
那么这是怎么实现的呢?之前我给UIView写过一个分类,实现通过一个方法调用给view对象添加点击事件(之后我觉得可以修改一下改成调用一个方法监听textField内容改变):
typedef void(^TapBlock)(UIView* view);
- (void)addBlock4Tap:(TapBlock)tapMBlock{
self.userInteractionEnabled=YES;
UITapGestureRecognizer* tapGes =
[[UITapGestureRecognizeralloc]initWithTarget:self
action:@selector(tapMethod:)];
[self addGestureRecognizer:tapGes];
__weakUIView* weakSelf =self;
weakSelf.tapBlock= tapMBlock;
}
- (void)tapMethod:(UIGestureRecognizer*)sender{
self.tapBlock(sender.view);
}
接着看下个:
[[self.usernameTextField.rac_textSignal
filter:^BOOL(id value){
NSString*text=value;
return text.length>3;
}]
subscribeNext:^(id x){
NSLog(@"%@",x);
}];
运行发现这个filter起到了过滤的作用(字如其意),当text的满足以上要求的时候才会执行subscribeNext,那么(id value)改成(NSString *text),(NSString *q),(NSString *aay)......会怎么样?库的方法filter()?subscribeNext?返回值是啥?
又一个新词:连贯接口
[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
return@(text.length);
}]
filter:^BOOL(NSNumber*length){
return[length integerValue]>3;
}]
subscribeNext:^(id x){
NSLog(@"%@",x);
}];
经过这两个例子,好像rac的方法连用的话是依次往下执行的!
如上图,一个新的signal内容block返回的值作为参数传入了这个signal对象下一步操作的方法里。上图的两个方法当然也可以连续调用,但是后面还会用到validPasswordSignal。
下面教程让输入的密码合理的时候背景透明,不合理的时候背景变黄。
RAC(self.passwordTextField,backgroundColor)=
[validPasswordSignal
map:^id(NSNumber*passwordValid){
return [passwordValid boolValue]?[UIColor clearColor]:[UIColor yellowColor];
}];
RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。(在我理解就是把一个signal对象给RAC宏,根据对象(1参数),属性(2)参数,从signal内部block的返回值取出来赋值上)
到此你可能会想,让username和password共同作为条件判断一个条件怎么实现:
RACSignal*signUpActiveSignal=
[RACSignal combineLatest:@[validUsernameSignal,validPasswordSignal]
reduce:^id(NSNumber*usernameValid,NSNumber*passwordValid){
return@([usernameValid boolValue]&&[passwordValid boolValue]);
}];
[signUpActiveSignal subscribeNext:^(NSNumber*signupActive){
self.signInButton.enabled=[signupActive boolValue];
}];
聚合信号,是这么说的,注意数组内的参数可以无限,reduce语法块的参数需要与数组一一对应,你可以试一下比数组多一个少一个不对应会有什么结果。
接下来用rac库给button添加点击事件:
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id x){
return [self signInSignal];
}]
subscribeNext:^(id x){
NSLog(@"Sign in result: %@",x);
}];
我发现好像map只是返回个对象,对对象进行加工并返回,好像所有的下一步逻辑页面等操作都能用subscribeNext,好像所有block参数在你知道上一个rac操作block返回值类型操作的情况下都能不用强转就写成对面类型的对象,而不是^(id x)。
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id x){
return[self signInSignal];
}]
subscribeNext:^(id x){
NSLog(@"Sign in result: %@",x);
}];
- (RACSignal*)signInSignal
{
return[RACSignal createSignal:^RACDisposable*(id subscriber) {
[self.signInServicesignInWithUsername:self.usernameTextField.textpassword:self.passwordTextField.textcomplete:^(BOOL success) {
[subscribersendNext:@(success)];
[subscribersendCompleted];
}];
return nil;
}];
}
用flattenmap而不是map是因为要是用map的success是上一个返回的值[self signInSignal],因为这本身就是一个signal所以打印出来其地址,而不是其block返回的值,flattenmap解决了这个问题!