什么是Functional Reactive Programming?
Functional Reactive Programming(以下简称FRP)是一种响应变化的编程范式
a = 2
b = 2
c = a + b // c is 4
b = 3 // now what is the value of c?
没错,如果使用FRP的话c
的值会随着b
的值变化而改变,这叫做「响应式编程」。
ReactiveCocoa(以下简称RAC)是在iOS开发中对FRP的实现FRP的核心是信号,
在RAC中信号使用RACSignal
表示
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。
下面就来一些简单的代码示例:
想获取nameTextFielt
的text
实时赋值给某一模型username
变量:
RAC(self, username) = self.nameTextFielt.rac_textSignal;
这是把nameTextFielt
的信号绑定到username
这个属性上来,
和这里是一样的:
[self.nameTextFielt.rac_textSignal subscribeNext:^(NSString* x) {
self.username = x;
}];
给nameTextFielt
的信号添加一个订阅者,这样每当text
的值变化的时候就会有信号产生,我们可以在block里拿到这个text的变化的值来做操作。
如果不是每个信号都会处理,比如我们只想知道当输入了6个字符之后的字符串,可以这样把信号过滤(filter)
[[self.nameTextFielt.rac_textSignal filter:^BOOL(NSString* value) {
return value.length > 6;
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
我们还可以映射(map)信号,比如想把用户输入的字符串转换为NSNumber类型:
[[self.nameTextFielt.rac_textSignal map:^id(NSString* value) {
return @(value.integerValue);
}]subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
当然,信号还能被合并(combine),在用户登录的时候如果希望当用户名和密码输入长度大于6之后登录按钮才能点击,反正则不能:
RACSignal *ableSignal = [RACSignal combineLatest:@[self.nameTextFielt.rac_textSignal, self.passwordTextFielt.rac_textSignal] reduce:^id(NSString *name, NSString *pswd){
return @( name.length>6 && pswd.length>6 );
}];
RAC(self.loginButton, enabled) = ableSignal;
但是我们通常并不这么干,而是去用RACSignal
去实例一个RACCommand
对象,并且为我们的登录按钮绑定该命令:
RACCommand *loginCommand = [[RACCommand alloc]initWithEnabled:ableSignal signalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//在这里做登录请求
BOOL success = YES;
if (success) { // 登录成功
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}else{ // 登录失败
[subscriber sendError:[NSError new]];
[subscriber sendCompleted];
}
return nil;
}];
}];
self.loginButton.rac_command = loginCommand;
这么做还有个好处,当subscriber
不进行sendCompleted
,即网络请求未返回时,按钮处于不可点击状态,可以防止重复提交
但是,如果我们想拿到网络请求返回的结果怎么办?
在上个代码中,如果网络请求成功的话,可以把返回结果返回:
[subscriber sendNext:result];
[[self.loginButton.rac_command execute:nil]subscribeNext:^(id x) {
// x 即为网络返回的数据
}];