目录
一、ReactiveCocoa结合了多种编程风格
二、在MVVM的项目中,为何总是会提到RAC呢?
三、ReactiveCocoa一些概念和用法
之前使用ReactiveCocoa+MVVM做过一次项目,当时使用它的原因就是感觉一个是新,一个是非常的简洁,看起来很酷。
当时别人问我ReactiveCocoa是什么,我都会回答:函数响应式编程。当时也不懂什么意思,那么现在再来回顾一下ReactiveCocoa。
一、ReactiveCocoa结合了多种编程风格:
函数式编程(Functional Programming)
函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
响应式编程(Reactive Programming)
响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果。
那么我们简单举个例子:
RACSignal *signalA = RACObserve(self, a);
RACSignal *signalB = RACObserve(self, b);
RAC(self.label, text) = [RACSignal combineLatest:@[signalA,signalB] reduce:^(NSInteger countA, NSInteger countB){
return [NSString stringWithFormat:@"结果:%ld",countA+countB];
}];
那看看我们通常的做法
- (void)sumAction
{
self.label.text = [NSString stringWithFormat:@"结果:%ld",self.a+self.b];
}
看起来这个操作放在了一个方法中,挺简单的,可是每次调用self.a = X;或者self.b = Y;时,想要显示正确的值,那么必定要再调用一次[self sumAction];
当我们执行顺序为self.a = X; [self sumAction]; self.b = Y; 这个时候没有得到我们想要的X+Y的值,这不是响应式的。
如果我们将[self sumAction];的调用放在set方法中呢?那么确实是响应式的了,但用到了至少两个方法。
在MVVM的项目中,为何总是会提到RAC呢?
MVVM从MVC演化而来,为的是减少C的逻辑和业务。如果没有RAC,那么我们得使用NSNotification、delegate、KVO等将V变化产生的数据传给VM,从而改变M的值。NSNotification可能跨度大点,尤其delegate和KVO会免不了将业务再次放到C中,如果不放到C中,那么这个VM就会与这个View绑定得比较紧,而ViewModel有可能并不是只服务于特定的一个View,这样的话使用更加松散的绑定关系能够降低ViewModel和View之间的耦合度。
因此View一旦产生数据了扔信号扔给ViewModel,使用ReactiveCocoa更能体现其精髓。
注:大部分MVVM架构都会使用ReactiveCocoa,但是使用ReactiveCocoa的iOS应用不一定就是基于MVVM架构的。MVVM的关键是要有View Model。
三、ReactiveCocoa一些概念和用法:
1.map:
map是将一张表中的值映射新值到另一张表中
NSArray*mappedArray=[array rx_mapWithBlock:^id(ideach){
return @(pow([each integerValue], 2));
}];
注:这是建立了一个新的数组,而不是将原数组的值进行改变
这所对应的是
NSMutableArray*mutableArray=[NSMutableArrayarrayWithCapacity:array.count];
for(NSNumber*numberinarray){[mutableArrayaddObject:@(pow([number integerValue], 2))];
}
NSArray*mappedArray=[NSArrayarrayWithArray:mutableArray];
这就是更高级别的函数更占优势的地方
2.filter(过滤)
3.fold(结合)
//合数值
NSNumber*sum=[array rx_foldWithBlock:^id(id memo,id each){
return @([memo integerValue] + [eachintegerValue]);
}];
//拼字符串
[[array rx_mapWithBlock:^id(id each){
return [each stringValue];
}] rx_foldInitialValue:@""block:^id(id memo,id each){
return [memo stringByAppendingString:each];
}];
4.Streams and Sequences
一系列的值抽象的被称为流,你可以认为流就像一个管道,其中值从一端进入从另一端放出。除非在管道的尾端当值出来时你能访问,访问过去的值甚至是当前值都是不可能的。没关系,我们拭目以待。
一系列的值,是吗?有点像一个列表,或在我们的例子中,一个数组。事实上,我们可以使用rac_sequence方法很容易地将一个NSArray转化成流。
NSArray*array=@[@(1),@(2),@(3)];
RACSequence*stream=[array rac_sequence];//将数组转化成流
[stream map:^id(id value){
return @(pow([value integerValue],2));
}];
NSLog(@"%@",[stream array]);//将流又转化成数组
//原来项目中使用了这个方法是这样的一个过程,map可以将字典转化成对象,然后就成为了对象的数组
NSLog(@"%@",[[[array rac_sequence] map:^id(idvalue){
return @(pow([value integerValue],2));
}] array]);
ReactiveCocoa包含left fold和right fold。left
fold是从开始往结尾穿过一个数组,right fold是反的
5.Signals
信号是另一种类型的流,对比sequences(序列),信号是推驱动,新值通过管道推压并且不能拉出,在以后他们把将被递送的数据抽取出来。
信号包括三种不同的类型值:Next、Error、Completion.
值得注意的是,Error和Completion只能通过信号送出一次,并且只有一个被送出。
6.Subscriptions
[self.textField.rac_textSignal subscribeNext:^(id x){
NSLog(@"New value:%@", x);
}error:^(NSError*error){
NSLog(@"Error: %@",error);
}completed:^{
NSLog(@"Completed.");
}];
在textfield中输入字符时,发现信号不发送error值和当信号deallocated的时候发送completion值,因此我们可以
[self.textField.rac_textSignal subscribeNext:^(idx){
NSLog(@"New value: %@", x);
}];
当subscribe(订阅)一个信号,将自动创建一个subscription对象,这个对象将自动保留,同时也保留了这个信号的subscribing状态,你可以手动处理订阅者,但这不是典型的操作,一般在使用重用视图的时候处理信号
7.Deriving State
宏RAC()有两个参数:一个对象和该对象的键值路径。然后,它执行一个单向的绑定右边的值到键值路径。值必须是对象,这就是为什么我们把布尔值包装成一个NSNumber。
RACSignal *validEmailSignal=[self.textField.rac_textSignalmap:^id(NSString *value){
return @([value rangeOfString:@"@"].location!= NSNotFound);
}];
RAC(self.button,enabled)=validEmailSignal;
RAC(self.textField,textColor)=[validEmailSignalmap:^id(id value){
if ([value boolValue]) { return [UIColor greenColor];
} else {
return [UIColor redColor];
}
}];
8.Commands
当你想要发送一个信号的值对用户交互事件响应,Commands是有用的。command的信号能被订阅来稍后接收返回的输出信号。
self.button.rac_command= [[RACCommand alloc]initWithEnabled:validEmailSignal
signalBlock:^RACSignal *(idinput){
NSLog(@"Button was pressed.");
return [RACSignal empty];
}];
这个按钮将保持disabled直到信号返回complete值(也可以是空值([RACSignal empty]))
9.RACSubject
信号提供者,自己可以充当信号,又能发送信号。
创建方法:
(1)创建RACSubject
(2)订阅信号
(3)发送信号
工作流程:
(1)订阅信号时,内部保存了订阅者,和订阅者响应block
(2)当发送信号的,遍历订阅者,调用订阅者的nextBlock
注:如果订阅信号,必须在发送信号之前订阅信号,不然收不到信号,这也有别于RACReplaySubject
-(void)racSubjectTest
{
RACSubject *subject= [RACSubjectsubject];
[subject subscribeNext:^(idx) {
NSLog(@"1
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
[subject subscribeNext:^(idx) {
NSLog(@"2
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
[subjectsendNext:@1];
[subject subscribeNext:^(idx) {
NSLog(@"3
%@,type:%@",x,NSStringFromClass(object_getClass(x)));
}];
}
10.Hot and Cold Signals
信号通常是懒惰的,意味着它们只有在有人已经订阅它们的时候,它们才会工作和发送信号。每增加一个订阅,工作就会重新执行。对于繁琐的操作,这是可以接受的,并且实际上这是所希望的。在ReactiveCocoa术语中,这种类型的信号被称为“冷信号”。
有时,我们想工作立即被执行,这种类型的信号被称为“热信号”。热信号非常罕见被使用到。
11.Multicasting
Multicasting是指某个信号订阅被共享于大量订阅者的术语。信号,一般是“冷信号”,它有时是不可取的,执行工作每次它都是订阅的“冷信号”,这常常是当副作用或工作时订阅是昂贵的时候执行,否则只有在适当的时候才会执行。网络请求浮现在脑海中。
于是我们创建来源于信号的RACMulticastConnection。你可以在RACSignal中使用publish方法或multicast:方法。前一种方法为您创建一个multicast的连接。后一种方法也做了同样的事,但还采用RACSubject参数。这个subject是手动从底层信号发送值,每当它被调用。然后,任何由底层信号发送的值有兴趣订阅连接的信号,相反(如果你提供一个subject,信号正好是这个subject)。
由于在默认情况下信号是“冷信号”,每添加一个订阅者,则它的工作被执行。如果是这样的话,那是不可取的,我们使用multicast的连接。
multicast的连接订阅的信号,当它已经通过了新的值,发送这些值到信号(这作为一个公共属性公开)。你可以多次订阅这个信号,并且在订阅时执行的工作,只是一次