ReactiveCocoa2.5基础知识
[toc]
ReactiveCocoa
是一个 iOS 中的函数式响应式编程框架,本文中简称RAC
。在开发 GitHub for Mac
过程中的一个副产品,它提供了一系列用来组合和转换值流的 API 。本文只是记录自己学到ReactiveCocoa的笔记。
ReactiveCocoa核心组件
信号源
RACStream 有两个派生类
RACSequence :基于空间的数据流(在RAC中很少使用)
创建
RACSequence *sequence1 = [RACSequence return:@1];
NSLog(@"%@",sequence1.head);
// 输出:1
RACSequence *sequence2 = [RACSequence sequenceWithHeadBlock:^id{
return @2;
} tailBlock:^RACSequence *{
return sequence1;
}];
NSLog(@"%@ %@",sequence2.head,sequence2.tail.head);
// 输出:2 1
RACSequence *sequence3 = @[@4,@5,@6,@7].rac_sequence;
for (id value in concatSquence) {
NSLog(@"%@",value);
}
// 输出:4
// 输出:5
// 输出:6
// 输出:7
变换
RACSequence *mapSrquence = [sequence1 map:^id(NSNumber *value) {
return @(value.integerValue *3); // 1 * 3
}];
NSLog(@"%@",mapSrquence.head);
// 输出:3
// concat:合并,目的:有两部分数据,想让上部分先执行,完了之后再让下部分执行(都可获取值)
RACSequence *concatSquence = [sequence2 concat:mapSrquence];
for (id value in concatSquence) {
NSLog(@"%@",value);
}
// 输出:2
// 输出:1
// 输出:3
concatSquence: 2 1 3
sequence3 : 4 5 6 7
RACSequence *zipSquence = [RACSequence zip:@[concatSquence,sequence3]];
遍历
for (RACTuple *tuple in zipSquence) {
NSLog(@"%@---%@",tuple.first,tuple.second);
}
// 输出:2---4
// 输出:1---5
// 输出:3---6
RACSignal :基于时间的数据流
1.使用基础
- 单元信号
RACSignal *signal1 = [RACSignal return:@1];
RACSignal *signal2 = [RACSignal error:errorObject];
RACSignal *signal3 = [RACSignal empty];
RACSignal *signal4 = [RACSignal never];
- 动态信号
RACSignal *signal5 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
// 如果出现错误,sendCompleted就会失效
// [subscriber sendError:errorObject];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
}];
}];
- Cocoa桥接
// 监听
RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame)];
// 事件点击
RACSignal *signal7 = [button rac_signalForControlEvents:UIControlEventTouchUpInside];
// dealloc
RACSignal *signal8 = [view rac_willDeallocSignal];
// KVO
RACSignal *signal9 = RACObserve(view, backgroundColor);
- 信号变换
RACSignal *signal10 = [signal1 map:^id(NSString *value) {
return [value substringFromIndex:1];
}];
- 序列转换
RACSequence *sequence = [RACSequence return:@1];
RACSignal *signal11 = sequence.signal;
2.订阅一个信号的方式
2.1 ) 订阅方法
// 有三种参数方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
example:
[signal11 subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"error");
} completed:^{
NSLog(@"finished");
}];
2.2 ) 绑定
RAC(view,backgroundColor) = signal10;
2.3 ) Cocoa桥接
// rac_liftSelector: 相当于多线程组;所有信号请求完成之后再执行方法,只需把三个结果传出去即可
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignals:signal1,signal3, nil];
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[signal1,signal1]];
[view rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalOfArguments:signal1];
3.单个信号的变换
3.1 ) 值操作
-
Map:映射,目的:把原始值value映射成一个新值
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [signalA map:^id(NSNumber *value) {
return @(value.integerValue * 3);
}];
[signalB subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:3
// 输出:6
// 输出:9
- mapReplace:Map简化操作,只返回一个相同的信号
RACSignal *signalC = [signalA mapReplace:@8];
[signalC subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:8
// 输出:8
// 输出:8
-
reduceEach:Map的变体,当 signalA 的值事件包裹的数据是 RACTuple 类型时,才可以使用该操作
RACSignal *signalD = [signalA reduceEach:^id(NSNumber *num1 , NSNumber *num2){
return @([num1 intValue] + [num2 intValue]);
}];
- not:需要传入的值都是NSNumber类型的,不是NSNumber类型会报错
- not操作会把每个NSNumber按照BOOL的规则,取非,当成新信号的值。
- (RACSignal *)not {
return [[self map:^(NSNumber *value) {
NSCAssert([value isKindOfClass:NSNumber.class], @"-not must only be used on a signal of NSNumbers. Instead, got: %@", value);
return @(!value.boolValue);
}] setNameWithFormat:@"[%@] -not", self.name];
}
- and操作需要原信号的每个信号都是元组RACTuple类型的,因为只有这样,RACTuple类型里面的每个元素的值才能进行&运算。
- and操作里面有3处断言。第一处,判断入参是不是元组RACTuple类型的。第二处,判断RACTuple类型里面至少包含一个NSNumber。第三处,判断RACTuple里面是否都是NSNumber类型,有一个不符合,都会报错。
- (RACSignal *)and {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
NSCAssert(tuple.count > 0, @"-and must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
return @([tuple.rac_sequence all:^(NSNumber *number) {
NSCAssert([number isKindOfClass:NSNumber.class], @"-and must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
return number.boolValue;
}]);
}] setNameWithFormat:@"[%@] -and", self.name];
}
- or操作的实现和and操作的实现大体类似。3处断言的作用和and操作完全一致,这里就不再赘述了。or操作的重点在any函数的实现上。or操作的入参也必须是RACTuple类型的。
- (RACSignal *)or {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, received: %@", tuple);
NSCAssert(tuple.count > 0, @"-or must only be used on a signal of RACTuples of NSNumbers, with at least 1 value in the tuple");
return @([tuple.rac_sequence any:^(NSNumber *number) {
NSCAssert([number isKindOfClass:NSNumber.class], @"-or must only be used on a signal of RACTuples of NSNumbers. Instead, tuple contains a non-NSNumber value: %@", tuple);
return number.boolValue;
}]);
}] setNameWithFormat:@"[%@] -or", self.name];
}
- 这里也有2个断言,第一个是确保传入的参数是RACTuple类型,第二个断言是确保元组RACTuple里面的元素各种至少是2个。因为下面取参数是直接从1号位开始取的。
- reduceApply做的事情和reduceEach基本是等效的,只不过变换规则的block闭包一个是外部传进去的,一个是直接打包在每个信号元组RACTuple中第0位中。
- (RACSignal *)reduceApply {
return [[self map:^(RACTuple *tuple) {
NSCAssert([tuple isKindOfClass:RACTuple.class], @"-reduceApply must only be used on a signal of RACTuples. Instead, received: %@", tuple);
NSCAssert(tuple.count > 1, @"-reduceApply must only be used on a signal of RACTuples, with at least a block in tuple[0] and its first argument in tuple[1]");
// We can't use -array, because we need to preserve RACTupleNil
NSMutableArray *tupleArray = [NSMutableArray arrayWithCapacity:tuple.count];
for (id val in tuple) {
[tupleArray addObject:val];
}
RACTuple *arguments = [RACTuple tupleWithObjectsFromArray:[tupleArray subarrayWithRange:NSMakeRange(1, tupleArray.count - 1)]];
return [RACBlockTrampoline invokeBlock:tuple[0] withArguments:arguments];
}] setNameWithFormat:@"[%@] -reduceApply", self.name];
}
// 举个例子
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
id block = ^id(NSNumber *first,NSNumber *second,NSNumber *third) {
return @(first.integerValue + second.integerValue * third.integerValue);
};
[subscriber sendNext:RACTuplePack(block,@1 , @3 , @5)];
[subscriber sendNext:RACTuplePack((id)(^id(NSNumber *x){return @(x.intValue * 10);}),@10,@20,@30)];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *signalB = [signalA reduceApply];
[signalB subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:16 = 1 + 3*5
// 输出:100 = 10 * 10
- materialize把信号包装成RACEvent类型。
- (RACSignal *)materialize {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [self subscribeNext:^(id x) {
[subscriber sendNext:[RACEvent eventWithValue:x]];
} error:^(NSError *error) {
[subscriber sendNext:[RACEvent eventWithError:error]];
[subscriber sendCompleted];
} completed:^{
[subscriber sendNext:RACEvent.completedEvent];
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"[%@] -materialize", self.name];
}
- dematerialize操作是materialize的逆向操作。它会把包装成RACEvent信号重新还原为正常的值信号。
- (RACSignal *)dematerialize {
return [[self bind:^{
return ^(RACEvent *event, BOOL *stop) {
switch (event.eventType) {
case RACEventTypeCompleted:
*stop = YES;
return [RACSignal empty];
case RACEventTypeError:
*stop = YES;
return [RACSignal error:event.error];
case RACEventTypeNext:
return [RACSignal return:event.value];
}
};
}] setNameWithFormat:@"[%@] -dematerialize", self.name];
}
3.2 ) 数量操作
- Filter:过滤
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"ab"];
[subscriber sendNext:@"hello"];
[subscriber sendNext:@"world"];
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalC = [signalA filter:^BOOL(NSString *value) {
return value.length > 3;
}];
[signalC subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:hello
// 输出:world
- Ignore:忽略
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Ab"];
[subscriber sendNext:@"hello"];
[subscriber sendNext:@"world"];
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [signalA filter:^BOOL(NSString *value) {
return ![value isEqual:@"ab"];
}];
[signalB subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:hello
// 输出:world
// 输出:123
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Ab"];
[subscriber sendNext:@"hello"];
[subscriber sendNext:@"world"];
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalC = [signalA ignore:@"ab"];
[signalC subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 输出:hello
// 输出:world
// 输出:123
ignoreValues:忽略所有的值
distinctUntilChanged:读取所有的值
-
Take:取值的数量
-
Skip:跳过
-
StartWith:初始位置添加
-
Repeat:重复说(一直不停止)
-
Retry:遇到错误继续尝试
副作用
DoNext:副作用操作,做一些事情
- (RACSignal *)doError:(void (^)(NSError *error))block;
- (RACSignal *)doCompleted:(void (^)(void))block;
- (RACSignal *)initially:(void (^)(void))block;
- (RACSignal *)finally:(void (^)(void))block;
-
Collect收集源信号的所有sendNext事件,直到源信号发出sendComplete事件,才将收集好的sendNext事件发出去
-
Aggregate折叠函数,和Scan这两个操作有些类似,但明显有区别,前者改变了事件流的事件数量,后者没有
-
Scan每次计算结果都会给到一个加和
3.3 ) 时间操作
-
Delay延迟
-
Throttle:阀门
3.4 ) 组合操作
-
Concat:连接
-
Merge:汇合
RACSignal *signalC = [signalA merge:signalB];
RACSignal *signalD = [RACSignal merge:@[signalA,signalB]];
RACSignal *signalE = [RACSignal merge:RACTuplePack(signalA,signalB)];
-
zip:拉链,目的:把两个信号压缩成一个信号,并且把两个信号的内容合并成一个元组
RACSignal *signalC = [signalA zipWith:signalB];
RACSignal *signalD = [RACSignal zip:@[signalA,signalB]];
RACSignal *signalE = [RACSignal zip:RACTuplePack(signalA,signalB)];
-
CombineLatest:和数学中的逻辑与(&&)的概念是一样的,只有两个两个信号同时发送了sendNext事件,才会触发
RACSignal *signalC = [signalA combineLatestWith:signalB];
RACSignal *signalD = [RACSignal combineLatest:@[signalA,signalB]];
-
Sample:采样
-
TakeUntil:当下一个信号传递的时候停止订阅信号
-
TakeUntilReplacement:当下一个信号传递的时候停止订阅信号,并返回加和下个信号
两者的区别
Pull-driver vs Push-driver
例子:看电影
Sequence:看本地下载的电影,突然想上厕所,可以暂停,上完厕所回来再看(观看者决定)
Signal:在线看电影,不管你看不看电影一直都在播放(发送者决定)
Data vs Event
Sequence: 任何ID类型的数据(值)
Signal:基于信号(值,错误,终止,中断)
订阅者
RACSubscriber
订阅过程
对于订阅过程不了解可以查看这三篇博客
细说ReactiveCocoa的冷信号与热信号(一)
细说ReactiveCocoa的冷信号与热信号(二)
细说ReactiveCocoa的冷信号与热信号(三)
RACSignal *signal5 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"dispose");
}];
}];
RACDisposable *disposable = [signal5 subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"error");
} completed:^{
NSLog(@"finished");
}];
[disposable dispose];
调度器
RACScheduler
RACScheduler
在 ReactiveCocoa
中就是扮演着调度器的角色,本质上,它就是用 GCD
的串行队列来实现的,并且支持取消操作。是的,在 ReactiveCocoa
中,并没有使用到 NSOperationQueue
和 NSRunloop
等技术,RACScheduler
也只是对 GCD
的简单封装而已。
清洁工
RACDisposable
RACDisposable
在 ReactiveCocoa
中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose
,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject
的 -dealloc
方法