Reactive的学习和使用
Podfile文件内容:
1 2 3 platform :ios, '7.0' pod 'ReactiveCocoa', '2.1.8'
运行$ pod install
安装完成后使用xcworkspace文件打开项目
在RWViewController.m 文件
1 #import <ReactiveCocoa/ReactiveCocoa.h>
在viewDidLoad中添加代码
1 2 3 [self.usernameTextField.rac_textSignal subscribeNext:^(id x) { NSLog(@"%@", x); }];
现在我们就能体验到信号编程了
可以看到我们每次输出一个字符都会产生一个信号,这大概就是信号编程吧 我们看看信号传递流程
现在我们将添加的代码修改一下
1 2 3 4 5 6 [[self.usernameTextField.rac_textSignal filter:^BOOL(id value) { NSString *text = value; return text.length > 3; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
我们可以看到,当满足filter的条件之后,才会产生信号,后面的条件才会继续执行,有一种链式操作的感觉 我们将上面的代码的完整结构
1 2 3 4 5 6 7 8 RACSignal *usernameSoucesSignal = self.usernameTextField.rac_textSignal; RACSignal *filteredUsername = [usernameSoucesSignal filter:^BOOL(id value) { NSString *text = value; return text.length > 3; }]; [filteredUsername subscribeNext:^(id x) { NSLog(@"%@", x); }];
可以看出来,每次文本框内容的改变就会产生一个信号对象,调用信号处理的block,得到结果 很显然,我们更喜欢上面那种简洁的语法
1 2 3 4 5 6 7 8 9 [[[self.usernameTextField.rac_textSignal map:^id(id value) { NSString *text = value; return @(text.length); }] filter:^BOOL(id value) { NSNumber *length = value; return [length integerValue] > 3; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
信号传递流程
我比较笨,假设你跟我一样,并没有发现有什么问题,现在我们再改改代码:
1 2 3 4 5 6 7 8 9 [[[self.usernameTextField.rac_textSignal map:^id(id value) { NSString *text = value; return text; }] filter:^BOOL(id value) { NSString *text = value; return text.length > 3; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
这下能看懂了吧,map解析字符串然后向下传递,filter通过返回YES来过滤字符串,subscribeNext得到过滤后的字符串,如果没有满足过滤条件,信号将不会向下传递,能理解信号传递的思路了么
1 2 3 4 5 6 7 8 9 RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal map:^id(id value) { NSString *text = value; return @([self isValidUsername:text]); }]; RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal map:^id(id value) { NSString *text = value; return @([self isValidPassword:text]); }];
得到验证信号后,解析然后设置文本框背景与用户交互 不过我们不采用下面的写法,使用更好的写法
1 2 3 4 5 6 7 [[validPasswordSignal map:^id(id value) { NSNumber *passwordValid = value; return [passwordValid boolValue] ? [UIColor clearColor] :[UIColor yellowColor]; }] subscribeNext:^(id x) { UIColor *color = x; self.passwordTextField.backgroundColor = color; }];
我们使用宏定义的方式将解析后返回的信号进行绑定处理
1 2 3 4 5 6 7 8 9 RAC(self.passwordTextField, backgroundColor) = [validPasswordSignal map:^id(id value) { NSNumber *passwordValid = value; return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor]; }]; RAC(self.usernameTextField, backgroundColor) = [validUsernameSignal map:^id(id value) { NSNumber *usernameValid = value; return [usernameValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor]; }];
可以看到代码,我们将文本框和背景颜色绑定为一个信号处理,当验证信号解析后将自动设置 我们来看看通常我们一般这样写
1 2 self.usernameTextField.backgroundColor = self.usernameIsValid ? [UIColor clearColor] : [UIColor yellowColor]; self.passwordTextField.backgroundColor = self.passwordIsValid ? [UIColor clearColor] : [UIColor yellowColor];
这个能看懂吧,平常我们是这样写的,只是改变了编程方式,有一种链式操作的感觉,能将信号绑定处理,其实还不错 信号传递流程是这样的
在viewDidLoad添加代码:
1 2 3 4 5 6 7 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]; }];
将两个信号组合生成一个新的信号进行解析处理
我们这样修改后,就可以移除下面的这些无用的代码了
1 2 3 4 5 6 7 8 9 10 @property (nonatomic) BOOL passwordIsValid; @property (nonatomic) BOOL usernameIsValid; // handle text changes for both text fields [self.usernameTextField addTarget:self action:@selector(usernameTextFieldChanged) forControlEvents:UIControlEventEditingChanged]; [self.passwordTextField addTarget:self action:@selector(passwordTextFieldChanged) forControlEvents:UIControlEventEditingChanged];
同时可以移除updateUIState
,usernameTextFieldChanged
和passwdTextFieldChanged
方法等并删除多余的代码 现在的信号传递流程是这样的
现在代码是不是更简洁,更方便
打开Main.storyboard
,删除绑定的sign up
按钮事件 继续在viewDidLoad中添加事件
1 2 3 [[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { NSLog(@"button clicked"); }];
可以看到下面的效果,事件也可以绑定为信号,感觉好6
现在我们可以移除signInButtonTouched:
事件,并创建信号 增加一个方法
1 2 3 4 5 6 7 8 9 10 - (RACSignal *)signInSingal { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [self.signInService signInWithUsername:self.usernameTextField.text password:self.passwordTextField.text complete:^(BOOL success) { [subscriber sendNext:@(success)]; [subscriber sendCompleted]; }]; return nil; }]; }
在viewDidLoad中添加代码
1 2 3 4 5 [[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) { return [self signInSingal]; }] subscribeNext:^(id x) { NSLog(@"Singal in result %@", x); }];
信号传递流程:
下面来修改一下代码:
1 2 3 4 5 6 7 [[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) { return [self signInSingal]; }] flattenMap:^RACStream *(id value) { return [self signInSingal]; }] subscribeNext:^(id x) { NSLog(@"Sign in result %@", x); }];
当点击按钮时验证通过得到1,验证失败得到0 现在我们让验证通过得到1时跳转
1 2 3 4 5 6 7 8 9 10 11 [[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) { return [self signInSingal]; }] flattenMap:^RACStream *(id value) { return [self signInSingal]; }] subscribeNext:^(NSNumber *signedIn) { BOOL success = [signedIn boolValue]; self.signInFailureText.hidden = success; if (success) { [self performSegueWithIdentifier:@"signInSuccess" sender:self]; } }];
现在,我们就使用ReactiveCocoa替代了原来的业务处理,比较简洁,但是也比较难理解 效果图:
修改代码,在信号传递中加一个过程
1 2 3 4 5 6 7 8 9 10 11 12 [[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) { self.signInButton.enabled = NO; self.signInFailureText.hidden = YES; }] flattenMap:^RACStream *(id value) { return [self signInSingal]; }] subscribeNext:^(NSNumber *signedIn) { self.signInButton.enabled = YES; BOOL success = [signedIn boolValue]; if(success) { [self performSegueWithIdentifier:@"signInSuccess" sender:self]; } }];
再运行看看,细节更加完善