RAC常用操作
信号的操作可以分为两类:单个信号的变换、多个信号的组合
单个信号的操作
单个信号的变换也可以分为几类:值操作、数量操作、时间操作;
1 值操作:映射
Map
: 将源信号内容映射成新对象;- 传入一个block,类型是返回对象,参数是value,value就是源信号的内容,直接拿到源信号的内容做处理
- 把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值
- 当 signalA 事件流出现完成事件时, signalB 的事件流也会出现完成事件
- 当 signalA 事件流出现错误事件时, signalB 也会将该错误原封不动地释放出来
RACSubject *subject = [RACSubject subject]; RACSubject *bindSingal = [subject map:^id(id value) { return [NSString stringWithFormat:@"The King: %@",value]; }]; [bindSingal subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subject sendNext:@"Jon Snow"];
FlattenMap
: 将源信号的内容映射成新的信号,并降堆;FlatternMap
中的Block返回信号,而Map中返回对象;- 若信号发出的是信号,则使用
FlatternMap
;RACSubject *singal = [RACSubject subject]; RACSubject *singalOfSingals = [RACSubject subject]; [[singalOfSingals flattenMap:^RACStream *(id value) { return value; }] subscribeNext:^(id x) { NSLog(@">>>>%@",x); }]; [singalOfSingals sendNext:singal]; [singal sendNext:@"123"];
map
操作还有一个简化版:mapReplace
,[signalA mapReplace:@886]
; 等价于[signalA map:^id(id value) { return @886; }]
; 。reduceEach
: 是map
的变体。当 signalA 的值事件包裹的数据是RACTuple
类型时,才可以使用该操作;除了
reduceEach
外,map
还有其他的一些变体操作:- (RACSignal *)not; - (RACSignal *)and; - (RACSignal *)or; - (RACSignal *)reduceApply;
reduceApply
也要求值事件包裹的数据类型是RACTuple
,并且该RACTuple
的第一个元素是一个block
,后面的元素作为该block
的参数传入,返回该block的执行结果。
2.1 数量操作:过滤
filter
: 一般和文本框一起用,添加过滤条件;- 当 signalA 产生完成事件时, signalB 也会产生完成事件
- 当 signalA 产生错误事件时, signalB 也会将该错误原封不动地释放出来
[[self.textField.rac_textSignal filter:^BOOL(id value) { return [value isEqualToString:@"王"]; }] subscribeNext:^(id x) { self.contentLabel.text = x; }];
ignore
: 忽略某些值,ignoreValues
忽略所有值;RACSubject *subject = [RACSubject subject]; [[subject ignore:@"Jon Snow"] subscribeNext:^(id x) { NSLog(@"ignore>>%@",x); }]; [subject sendNext:@"Ned Stark"]; [subject sendNext:@"Bobb Stark"]; [subject sendNext:@"Jon Snow"]; //忽略 [subject sendNext:@"Sansa Stark"];
skip:(NSUInteger)skipCount
: 跳过前N个值;RACSubject *subject = [RACSubject subject]; [[subject skip:2] subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subject sendNext:@"Ned Stark"];//过滤 [subject sendNext:@"Bobb Stark"];//过滤 [subject sendNext:@"Jon Snow"];
distinctUntilChanged
: 如果当前值和上一次的值一样,就过滤掉;RACSubject *subject = [RACSubject subject]; [[subject distinctUntilChanged] subscribeNext:^(id x) { NSLog(@"订阅:>>%@",x); }]; [subject sendNext:@"Jon"]; [subject sendNext:@"Bobb"]; [subject sendNext:@"Jon"]; [subject sendNext:@"King"]; [subject sendNext:@"King"];//过滤
take:(NSUInteger)count
: 只取前N个值 ;takeLast:(NSUInteger)count
: 只取后N个值RACSubject *subject = [RACSubject subject]; [[subject take:2] subscribeNext:^(id x) { NSLog(@"take>>%@",x); }]; [[subject takeLast:2] subscribeNext:^(id x) { NSLog(@"takeLast>>%@",x); }]; [subject sendNext:@"Ned Stark"]; [subject sendNext:@"Bobb Stark"]; [subject sendNext:@"Jon Snow"]; [subject sendNext:@"Sansa Stark"]; [subject sendCompleted]; //必须要调用sendComplete
2.2 其他数量操作
startWith
: 操作的作用是在事件流的开始新增一个值事件。repeat
: 操作会忽略 signalA 的完成事件,但它不会忽略错误事件,换句话说,如果 signalA 的事件流中含有错误事件,那么 signalB 的事件流会和 signalA 完全一致。retry
: 当 signalA 事件流中含有完成事件时,那么 signalA 的事件流会和 signalA 完全一致,retry 操作还有带参数版本 retry: ,该版本可以指定次数。这种操作在处理网络任务时非常有用Collect
: 聚合数量。Aggregate
和Scan
: 这两个操作有些类似,但明显有区别,前者改变了事件流的事件数量,后者没有。
3 时间操作
经常会有这样的需求:定时产生一个事件。
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler; + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway;
这两个类方法都会产生一个时间信号,时间信号会以一定频率产生一个值事件,值事件包裹的是
NSDate
对象。第二个方法中的参数leeway
是「缓冲」、「余地」的意思,还不太明白其作用Delay
Throttle
在下图中, signalA 事件流中一共有5个值事件和1个完成事件,其中1号和2号事件间隔2s,2、3、4号事件间隔1s,5号值事件比4号值事件晚3s,完成事件比5号事件晚1s。- signalB 由 [signalA throttle:1.5] 得到,throttle表示门限,其作用效果是:
- 当 signalA 事件流产生一个值事件时,若1.5s内没有其他的值事件产生,则 signalB 事件流中也会产生该值事件;比如上图中的1号值事件,下一个值事件(2号)在其2s后产生,满足要求,故而在1.5s后, signalB 的事件流也产生该信号;
- 当 signalA 事件流产生一个值事件时,若1.5s内有其他的值事件产生,则 signalB 会过滤掉该值事件;比如上图中的2号和3号值事件,在1s后分别有3号和4号信号产生,不满足要求,故而都不会出现在 signalB 的事件流中; 对于 signalA 中的最后一个值事件, signalB 事件流中总会也包含它。 这种信号有什么用呢?有一个常用的应用场景:在App内经常需要搜索,为了确保实时性,简单的做法是每输入一个字符就检索一下,但是若用户输入字符比较快,这种检索策略会比较浪费流量,因此比较好的做法是,用户输入某个字符后,1s(参考值)内没有再输入别的字符,就检索一次搜索结果。此时throttle就有了用武之地。
多个信号的组合
以 signalA + signalB => signalC 为例,需要留意几个问题: 组合得到 signalC 的信号受哪个信号终止而终止, signalA or signalB ?当 signalA 或 signalB 事件流中出现错误信号,会如何?各个信号何时开始被订阅?
concat
: 使用需求:有两部分数据:想让上部分先执行,完了之后再让下部分执行(都可获取值);当 signalA 的事件流中出现完成事件时,立马订阅 signalB , signalC 事件流的完成事件与 signalB 的完成事件对应。同时,注意到当 signalA 中出现错误事件时, signalC 中的事件流与 signalA 的事件流完全保持一致,就没 signalB 什么事儿了。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"创建信号源A..."); [subscriber sendNext:@"King"]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"信号源A销毁..."); }]; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"创建信号源B..."); [subscriber sendNext:@"Queen"]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"信号源B销毁..."); }]; }]; RACSignal *concatSignal = [signalA concat:signalB]; [concatSignal subscribeNext:^(id x) { NSLog(@"%@",x); }];
combineLatest
: 把多个信号聚合成你想要的信号,使用场景:当多个输入框都有值的时候按钮才可点击。 思路:就是把输入框输入值的信号都聚合成按钮是否能点击的信号。RACSignal *combineSignal = [RACSignal combineLatest:@[_nameField.rac_textSignal,_pwdField.rac_textSignal,_textField.rac_textSignal] reduce:^id(NSString *name,NSString *pwd,NSString *content){ NSLog(@"%@,%@,%@",name,pwd,content); return @(name.length && pwd.length && content.length); }]; RAC(_observerBtn,enabled) = combineSignal;
除了 signalC = [signalA combineLatest:signalB] 这种用法外,还可以这样:
signalC = [RACSignal combineLatest:@[signalA, signalB]]; signalC = [RACSignal combineLatest:RACTuplePack(signalA, signalB)];
zipWith
: 把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元祖,才会触发压缩流的next事件。RACSubject *subjectA = [RACSubject subject]; RACSubject *subjectB = [RACSubject subject]; RACSignal *zipSignal = [subjectA zipWith:subjectB]; [zipSignal subscribeNext:^(id x) { NSLog(@"%@",x); ////所有的值都被包装成了元组 RACTuple }]; [subjectA sendNext:@"King"]; [subjectB sendNext:@"Queen"];
除了 signalC = [signalA zip:signalB] 这种用法外,还可以这样:
signalC = [RACSignal zip:@[signalA, signalB]]; signalC = [RACSignal zip:RACTuplePack(signalA, signalB)];
merge
: 多个信号合并成一个信号,任何一个信号有新值就会调用,任何一个信号请求完成都会被订阅到;除了 signalC = [signalA merge:signalB] 这种用法外,还可以这样:
signalC = [RACSignal merge:@[signalA, signalB]]; signalC = [RACSignal merge:RACTuplePack(signalA, signalB)];
//merge基本使用 RACSubject *subjectA = [RACSubject subject]; RACSubject *subjectB = [RACSubject subject]; RACSubject *subjectC = [RACSubject subject]; RACSignal *mergeSignal = [subjectA merge:subjectB]; RACSignal *finalSignal = [mergeSignal merge:subjectC]; [finalSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subjectA sendNext:@"King"]; [subjectB sendNext:@"Queen"]; [subjectC sendNext:@"Kin"];
Sample
: 在 signalC = [signalA sample:signalB] 中, signalB 充当 signalA 的采样信号,一旦 signalA 或 signalB 先产生完成事件或者错误事件, signalC 的事件流就被终止。takeUntil:(RACSignal *)
: 给takeUntil
传的是哪个信号,那么当这个信号发送信号或sendCompleted
,就不能再接受源信号的内容了;RACSubject *subject = [RACSubject subject]; RACSubject *subject2 = [RACSubject subject]; [[subject takeUntil:subject2] subscribeNext:^(id x) { NSLog(@"%@",x); }]; [subject sendNext:@"Ned Stark"]; [subject sendNext:@"Bobb Stark"]; [subject2 sendNext:@"Jon Snow"]; // [subject2 sendCompleted]; [subject sendNext:@"Sansa Stark"]; //不再接受
takeUntilReplacement:(RACSignal *)
: signalC 事件流中的事件和 signalA 一一对应,一旦 signalB 事件流中出现了信号, signalC 的事件就和 signalB 形成呼应,当然前提是 signalC 的事件流还没有终结。then
: 使用需求:有两部分数据:想让上部分先进行网络请求但是过滤掉数据,然后进行下部分的,拿到下部分数据。RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"创建信号源A..."); [subscriber sendNext:@"King"]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"信号源A销毁..."); }]; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"创建信号源B..."); [subscriber sendNext:@"Queen"]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"信号源B销毁..."); }]; }]; RACSignal *thenSignal = [signalA then:^RACSignal *{ return signalB; }]; [thenSignal subscribeNext:^(id x) { NSLog(@"%@",x); }];
switchToLatest
// 创建信号中信号
RACSubject *signalofsignals = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
// 订阅信号
[signalofsignals subscribeNext:^(RACSignal *x) {
[x subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}];
// switchToLatest: 获取信号中信号发送的最新信号
[signalofsignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[signalofsignals sendNext:signalA];
[signalA sendNext:@4];
RAC开发中常见用法。
代替代理:
rac_signalForSelector
:用于替代代理。// rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。 [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) { NSLog(@"点击红色按钮"); }];
代替KVO :
rac_valuesAndChangesForKeyPath
:用于监听某个对象的属性改变。// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号 // observer:可以传入nil [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) { NSLog(@"%@",x); }];
监听事件:
rac_signalForControlEvents
:用于监听某个事件(非自定义控件)。// 把按钮点击事件转换为信号,点击按钮,就会发送信号 [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { NSLog(@"按钮被点击了"); }];
代替通知:
rac_addObserverForName
:用于监听某个通知。// 把监听到的通知转换信号 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) { NSLog(@"键盘弹出"); }];
监听文本框文字改变:
rac_textSignal
:只要文本框发出改变就会发出这个信号。[_textField.rac_textSignal subscribeNext:^(id x) { NSLog(@"文字改变了%@",x); }];
处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals
:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。- 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
// 处理多个请求,都返回结果的时候,统一做处理. RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"发送请求1"]; return nil; }]; RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"发送请求2"]; return nil; }]; // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。 [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]]; } // 更新UI - (void)updateUIWithR1:(id)data r2:(id)data1 { NSLog(@"更新UI%@ %@",data,data1); }
ReactiveCocoa常见宏
RAC(TARGET, [KEYPATH, [NIL_VALUE]])
:用于给某个对象的某个属性绑定。// 只要文本框文字改变,就会修改label的文字 RAC(self.labelView,text) = _textField.rac_textSignal;
RACObserve(self, name)
:监听某个对象的某个属性,返回的是信号。[RACObserve(self.view, center) subscribeNext:^(id x) { NSLog(@"%@",x); }];
@weakify(Obj)
和@strongify(Obj)
,一般两个都是配套使用,在主头文件ReactiveCocoa.h
中并没有导入,需要自己手动导入,RACEXTScope.h
才可以使用。但是每次导入都非常麻烦,只需要在主头文件自己导入就好了(解决循环引用问题,弱化/强化指针)。RACTuplePack/RACTupleUnpack
:把数据包装成RACTuple(元组类)/把RACTuple(元组类)解包成对应的数据。// 把参数中的数据包装成元组 RACTuple *tuple = RACTuplePack(@"xmg",@20); // 解包元组,会把元组的值,按顺序给参数里面的变量赋值 // name = @"xmg" age = @20 RACTupleUnpack(NSString *name,NSNumber *age) = tuple;