函数响应式编程(FRP)从入门到"放弃"——图解RACSignal篇
目录
- 1.RACSignal的创建
- 2.RACSignal的订阅
- 3.RACSignal各类操作
一.RACSignal的创建
1.创建单元信号
NSError *errorObject = [NSError errorWithDomain:@"Something wrong" code:500 userInfo:nil];
//基本的4种创建方法
RACSignal *signal1 = [RACSignal return:@"Some Value"];
RACSignal *signal2 = [RACSignal error:errorObject];
RACSignal *signal3 = [RACSignal empty];
RACSignal *signal4 = [RACSignal never];
2.创建动态信号
RACSignal *signal5 = [RACSignal createSignal:
^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendError:errorObject];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
}];
}];
3.通过Cocoa桥接方式获得一个信号
RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame:)];
RACSignal *signal7 = [view rac_signalForControlEvents:UIControlEventTouchUpInside];
RACSignal *signal8 = [view rac_willDeallocSignal];
//KVO的原理实现
RACSignal *signal9 = RACObserve(view, backgroundColor);
4.通过信号变换获得
RACSignal *signal10 = [signal1 map:^id(NSString *value) {
return [value substringFromIndex:1];
}];
5.通过序列转换获得
RACSequence *sequence = @[@"A", @"B", @"C"].rac_sequence;
//这里转换之后获得的signal11是一个很快把信号值吐出的一个信号
RACSignal *signal11 = sequence.signal;
二.RACSignal的订阅
1.订阅方法
[signal11 subscribeNext:^(id x) {
NSLog(@"next value is %@", x);
} error:^(NSError *error) {
NSLog(@"Ops! Get some error: %@", error);
} completed:^{
NSLog(@"It finished success");
}];
2.绑定方法
//这里相当于KVO,signal每次产生一个color,都是对backgroundColor进行赋值
RAC(view, backgroundColor) = signal10;
3.Cocoa桥接
[view rac_liftSelector:@selector(convertPoint:toView:)
withSignals:signal1, signal2, nil];
[view rac_liftSelector:@selector(convertRect:toView:)
withSignalsFromArray:@[signal3, signal4]];
[view rac_liftSelector:@selector(convertRect:toLayer:)
withSignalOfArguments:signal5];
值得说明的是:如果selector有返回值,那么调用rac_liftSelector会得到一个新的信号。新的信号是调用selector之后的返回值。
4.订阅过程的执行过程
RACSignal *signal = [RACSignal createSignal:
^RACDisposable *(id<RACSubscriber> subscriber)
{
1 [subscriber sendNext:@1];
2 [subscriber sendNext:@2];
3 [subscriber sendCompleted];
4 return [RACDisposable disposableWithBlock:^{
5 NSLog(@"dispose");
}];
}];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
6 NSLog(@"next value is %@", x);
} error:^(NSError *error) {
7 NSLog(@"Ops! Get some error: %@", error);
} completed:^{
8 NSLog(@"It finished success");
}];
9 [disposable dispose];
如果一个signal信号被订阅,那么就会先进入到信号创建的block中,当运行到sendNext,sendCompleted,sendError,接下来就会走到订阅者的对应的subscribeNext,completed,error对应的block块中。主要顺序还是在signal的创建的block中进行执行,只是到了特性的sendNext,sendCompleted,sendError,才会跳到订阅者的对应的block中去执行。订阅者的相应的block执行完了之后,再次回到signal信号的block中继续往下执行。最后signal信号执行完之后会执行disposable。当signal信号发现了disposable,会立即调用创建signal信号里面RACDisposable的block块。
执行过程:1-6-2-6-3-8-4-5-9
RACStream 和 RACSignal的区别 在于 返回数据还是返回事件。
事件总共3种 :值,错误,结束。
值可能是OC对象,RACTuple,甚至是一个RACSignal。
先来说一下RACTuple,RACTuple也是RAC定义的一种数据类型,可以说是NSArray的简化版
RACTuple *tuple = RACTuplePack(@1, @"haha");
id first = tuple.first;
id second = tuple.second;
id last = tuple.last;
id index1 = tuple[1];
RACTupleUnpack(NSNumber *num, NSString *str) = tuple;
这里有两个宏,RACTuplePack是可以把后面2个对象打包成一个RACTuple。RACTuple也实现了下标访问。使用的过程中并不是id类型,所以需要强转一下类型。类似于这种用法,RACTupleUnpack(NSNumber *num, NSString *str)。
三.RACSignal的各类操作
先来约定好图例
1.单个信号的变换
- 1.对值的操作
- 2.对数量的操作
- 3.对维度的操作
- 4.对时间间隔的操作
1.对值的操作
- Map操作
- MapReplace操作
- ReduceEach操作
- not操作
- and操作
- or操作
- reduceApply操作
- materialize操作
- dematerialize操作
(1)Map操作
RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
RACSignal *SignalB = [signalA map:^id(NSNumber *value) {
return @(value.integerValue * 2);
}];
如果Map过程中遇到了Error,Error并不会进行Map,而是直接Error返回
(2)MapReplace操作
RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
RACSignal *signalB = [signalA map:^id(id value) {
return @8;
}]; // signalB is --8--8--8--8--|
RACSignal *signalC = [signalA mapReplace:@8];
// signalC is --8--8--8--8--| too.
(3)ReduceEach操作
RACTuple *a = RACTuplePack(@1, @2);
RACTuple *b = RACTuplePack(@2, @3);
RACTuple *c = RACTuplePack(@3, @5);
RACSignal *signalA = @[a, b, c].rac_sequence.signal;
RACSignal *signalB = [signalA reduceEach:^id(NSNumber *first,
NSNumber *second) {
return @(first.integerValue + second.integerValue);
}];
reduceEach后面可以跟一些具体类型的参数。
(4)not操作
RACSignal *signalA = @[@0, @1, @1, @0].rac_sequence.signal;
RACSignal *signalB = [signalA not];
(5)and操作
RACTuple *a = RACTuplePack(@0, @1);
RACTuple *b = RACTuplePack(@0, @0);
RACTuple *c = RACTuplePack(@1, @1);
RACTuple *d = RACTuplePack(@1, @0);
RACSignal *signalA = @[a, b, c, d].rac_sequence.signal;
RACSignal *signalB = [signalA and];
(6)or操作
RACTuple *a = RACTuplePack(@0, @1);
RACTuple *b = RACTuplePack(@0, @0);
RACTuple *c = RACTuplePack(@1, @1);
RACTuple *d = RACTuplePack(@1, @0);
RACSignal *signalA = @[a, b, c, d].rac_sequence.signal;
RACSignal *signalB = [signalA or];
(7)reduceApply操作
这里把一个block当做RACTuple的first,然后second,third是其他的参数,当执行reduceApply操作的时候,就会取出第一个参数的block,然后把第二个和第三个参数代入这个block中。
(8)materialize操作
这个操作会把普通的Complete,Error,Next事件,都变成普通的值事件传递出来。
(9)dematerialize操作
RACEvent有一些值
RACEventTypeCompleted,
RACEventTypeError,
RACEventTypeNext
使用dematerialize操作之后,可以把上面的值重新转换成对应的事件。
(10)Scan操作
RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
RACSignal *signalB = [signalA scanWithStart:@0
reduce:^id(NSNumber *running,
NSNumber *next) {
return @(running.integerValue + next.integerValue);
}];
对比下面对数量操作里面的Aggregate操作,这里的优点很明显,每次“扫描”都可以拿到一个值。
- (RACSignal *)scanWithStart:(id)startingValue
reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock;
上面这个是scan的变种函数。
2.对数量的操作
- Filter操作
- Ignore / IgnoreValues操作
- DistinctUntilChanged操作
- Take操作
- Skip操作
- StartWith操作
- Repeat操作
- Retry操作
- Collect操作
- Aggregate操作
(1)Filter操作
RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
RACSignal *signalB = [signalA filter:^BOOL(NSString *value) {
return value.length > 2;
}];
(2)Ignore / IgnoreValues操作
Ignore操作
RACSignal *signalA = @[@1, @2, @1, @3].rac_sequence.signal;
RACSignal *signalB = [signalA filter:^BOOL(id value) {
return ![@1 isEqual:value];
}];
RACSignal *signalC = [signalA ignore:@1];
IgnoreValues操作
进行这个操作之后,新信号就没有值了,只剩下Error,Complete事件了。
RACSignal *signalA = @[@1, @2, @1, @3].rac_sequence.signal;
RACSignal *signalB = [signalA ignoreValues];
(3)DistinctUntilChanged操作
这个操作其实就是去重操作。
RACSignal *signalA = @[@1,@1,@2,@2,@3].rac_sequence.signal;
RACSignal *signalB = [signalA distinctUntilChanged];
(4)Take操作
RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
RACSignal *signalB = [signalA take:2];
(5)Skip操作
RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
RACSignal *signalB = [signalA skip:2];
take和Skip变种方法
- (RACSignal *)takeLast:(NSUInteger)count;
- (RACSignal *)takeUntilBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)takeWhileBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)skipUntilBlock:(BOOL (^)(id x))predicate;
- (RACSignal *)skipWhileBlock:(BOOL (^)(id x))predicate;
predicate是传入一个要不要或者跳不跳过的规则。
(6)StartWith操作
RACSignal *signalA = @[@"ab", @"hello", @"app", @"1"].rac_sequence.signal;
RACSignal *signalB = [signalA startWith:@"Start"];
(7)Repeat操作
RACSignal *signalA = @[@"ab", @"hello"].rac_sequence.signal;
RACSignal *signalB = [signalA repeat];
(8)Retry操作
在一些网络请求中,如果我们把网络获取封装成一个信号,网络请求失败的时候,这时候就会抛出一个错误,网络不能连接。这个时候客户端可能会有重新连接的需求。
RACSignal *signalA = @[@"ab"].rac_sequence.signal;
RACSignal *signalB = [signalA retry:2];
Retry后面是重试的次数
这里会引出副作用操作
RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
RACSignal *signalB = [signalA map:^id(id value) {
// do some thing;
return value;
}];
RACSignal *signalC = [signalA doNext:^(id x) {
// do some thing;
}];
副作用操作就是指的是不影响值,额外做的一些操作,比如说做一些动画,屏幕是打出一些字,弹框等等。
有以下一些便捷方法
- (RACSignal *)doError:(void (^)(NSError *error))block;
- (RACSignal *)doCompleted:(void (^)(void))block;
- (RACSignal *)initially:(void (^)(void))block;
- (RACSignal *)finally:(void (^)(void))block;
(9)Collect操作
RACSignal *signalA = @[@"ab", @"hello", @"ppp", @"0"].rac_sequence.signal;
RACSignal *signalB = [signalA collect];
(10)Aggregate操作
这个函数其实是一个“折叠函数”
RACSignal *signalA = @[@1, @2, @3, @4].rac_sequence.signal;
RACSignal *signalB = [signalA aggregateWithStart:@0
reduce:^id(NSNumber *running,
NSNumber *next) {
return @(running.integerValue + next.integerValue);
}];
值得注意的是,这个函数在终止的时候才有返回,不终止就一直不返回,如果遇到了Repeat信号,那就一直都不会返回了。aggregateWithStart有一个初始值。缺点是变化中拿不到值,只有最后才有值,对比值操作里面的Scan操作,还是Scan操作比较好。
- (RACSignal *)aggregateWithStart:(id)start
reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock;
- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory
reduce:(id (^)(id running, id next))reduceBlock;
这个是aggregate的变种函数。
3.对时间间隔的操作
- 单位间隔时间信号
- Delay操作
- Throttle操作(节流操作)
(1)单位间隔时间信号
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway;
上面这2个方法,会返回以interval为时间间隔的时间信号。每隔interval就会发出一个信号。
(2)Delay操作
将源信号延迟1秒发送。
RACSignal *signalB = [signalA delay:1];
(3)Throttle操作(节流操作)
RACSignal *signalB = [signalA throttle:2];
throttle后面跟着是间隔多少秒。如果间隔多少秒之后都没有值,就把之前监听到的值发出来。比如上述的例子,1发出来之后,间隔2秒,2秒内又来了2,继续监听2秒,2秒内又来了3,继续监听2秒,2秒内没有其他的值了,这时候就把3发送出来。如此道理,会发出5,6。
这个函数在我们开发中会用到的场景是,搜索操作。
用户拼命在搜索框内输入,这个时候如果每输入一次就网络请求一次,那又浪费流量,又没有必要。这个时候就可以用到这个throttle了。用户输完1秒内没有输入了,这个时候就可以去请求网络了。
- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate;
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
这些是Throttle的变种函数。
2.多个信号的组合
- 1.Concat组合操作
- 2.Merge组合操作
- 3.Zip组合操作
- 4.CombineLatest组合操作
- 5.Sample组合操作
- 6.TakeUntil / TakeUntilReplacement组合操作
1.Concat组合操作
RACSignal *signalA = @[@1, @2, @3, @4, @5].rac_sequence.signal;
RACSignal *signalB = @[@6, @7].rac_sequence.signal;
RACSignal *signalC = [signalA concat:signalB];
Concat工作原理:先订阅A,当A都结束了之后才会继续订阅B,这个时候可以看到,B早已结束了。这里也体现出了一个信号的定义和一个信号的订阅,是分离的。
如果A发生了错误,那就没有B什么事了。
如果B发生了一个错误,那么C就会传递这个错误。
2.Merge组合操作
RACSignal *signalA = @[@1, @2, @3, @4, @5].rac_sequence.signal;
RACSignal *signalB = @[@6, @7].rac_sequence.signal;
RACSignal *signalC = [signalA merge:signalB];
RACSignal *signalC = [RACSignal merge:@[signalA, signalB]];
RACSignal *signalC = [RACSignal merge:RACTuplePack(signalA, signalB)];
如果SignalA,SignalB,是2个线程,那么SignalC会穿梭在2个线程中发送。如上图的例子,1,2,3,5发在线程A,4,6,7发在线程B里。
Merge在实际开发中的使用场景可能会出现如下场景:
RACSignal *appearSignal = [[self rac_signalForSelector:@selector(viewDidAppear:)]
mapReplace:@YES];
RACSignal *disappearSignal = [[self rac_signalForSelector:@selector(viewWillDisappear:)]
mapReplace:@NO];
RACSignal *activeSignal = [RACSignal merge:@[appearSignal, disappearSignal]];
activeSignal信号得到的就是app是否在前台的信号。
3.Zip组合操作
RACSignal *signalA = @[@1, @2, @3, @5].rac_sequence.signal;
RACSignal *signalB = @[@4, @6, @7].rac_sequence.signal;
RACSignal *signalC = [signalA zipWith:signalB];
RACSignal *signalC = [RACSignal zip:@[signalA, signalB]];
RACSignal *signalC = [RACSignal zip:RACTuplePack(signalA, signalB)];
注意图中的RACTuple的颜色。谁来控制终止由谁短来控制。发送的时机是谁更晚,就发送谁。(1,4)4更晚,就在4的时候发送RACTuple,(2,6)6更晚,6的时候发送RACTuple。(3,7)3更晚,3的时候发送RACTuple。
4.CombineLatest组合操作
RACSignal *signalA = @[@1, @2, @3].rac_sequence.signal;
RACSignal *signalB = @[@4, @6, @7].rac_sequence.signal;
RACSignal *signalC = [signalA combineLatestWith:signalB];
RACSignal *signalC = [RACSignal combineLatest:@[signalA, signalB]];
RACSignal *signalC = [RACSignal combineLatest:RACTuplePack(signalA, signalB)];
永远都结合最新的值作为新值返回。谁来控制终止由谁长来控制。
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;
+ (RACSignal *)zip:(id<NSFastEnumeration>)streams reduce:(id (^)())reduceBlock;
这分别是对应的combineLatest和Zip的语法糖似的便捷操作。都只不过是在后面加了一个Reduce。
5.Sample组合操作
RACSignal *signalC = [signalA sample:signalB];
谁早结束,那么结果信号就跟着谁一起结束。
如果遇到了错误,这个函数相当于快门。一样会把Error取得,并返回。
6.TakeUntil / TakeUntilReplacement组合操作
RACSignal *signalC = [signalA takeUntil:signalB];
当出现B的时候,就终止A。
RACSignal *signalC = [signalA takeUntilReplacement:signalB];
当出现B的时候,就终止A,后面再紧接着B。