RAC总结

ReactiveCocoa 是一个函数响应式编程(FRP)的框架。

一、导入框架

如果是纯OC项目,使用pod 'ReactiveObjC'导入OC版RAC即可。
如果是纯Swift项目,按照下图方式导入:

RAC-Swift.png

由于Swift要使用动态库,所以加入use_frameworks!这行代码。

二、信号类RACSignal

RACSiganl:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。

信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。

  • 获得一个信号的方式

1.单元信号

RACSignal *signal1 = [RACSignal return:@"value"];
RACSignal *signal2 = [RACSignal error:error];
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:error];
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{
        
    }];
}];

3.Cocoa桥接

// 监测对象的该方法是否调用
RACSignal *signal6 = [view rac_signalForSelector:@selector(setFrame:)];
// 监测对象是否触发了该事件
RACSignal *signal7 = [button rac_signalForControlEvents:UIControlEventTouchUpInside];
// 对象将要释放时发出该信号
RACSignal *signal8 = [view rac_willDeallocSignal];
// 监测对象的某个属性是否变化,一旦变化就会发出信号
RACSignal *signal9 = RACObserve(view, backgroundColor);

4.信号变换

// 对信号每发出一个值进行处理,再返给信号
RACSignal *signal10 = [signal map:^id(NSString *value) {
    return [value substringFromIndex:1];
}];

5.序列转换

// 集合也能被转换为信号进行操作
RACSignal *signal11 = collection.rac_sequence.signal;
  • 订阅一个信号的方式

1.订阅方法

// 直接调用 subscribeNext 方法进行订阅
[signal11 subscribeNext:^(id x) {
    NSLog(@"value %@", x);
} error:^(NSError *error) {
    NSLog(@"error %@", error);
} completed:^{
    NSLog(@"finished");
}];

2.绑定

// 将对象属性与信号值绑定
RAC(self.loginButton, enabled) = signal;

3.Cocoa桥接

// 两个信号都sendNext后才会调用selector
[view rac_liftSelector:@selector(data1:data2:) withSignalsFromArray:@[signal6, signal7]];
  • 值操作

1.Map

// 把信号A中的值取出乘2后返回给信号B
RACSignal *signalB = [signalA map:^id(NSNumber *value) {
    return @(value.integerValue * 2);
}];

2.MapReplace

// 把信号A中的值都替换为8
RACSignal *signalC = [signalA mapReplace:@8];
// 效果等同于
RACSignal *signalB = [signalA map:^id(id value) {
    return @8;
}];

3.ReduceEach

// reduceEach与map类似,不过只有signalA包含的值是元组类型时才能用它
// 信号A中包含了N个元组,每个元组内有两个元素,使用 reduceEach 将每个元组中的两个数字取出并相加,返回给信号B
RACSignal *signalB = [signalA reduceEach:^id(NSNumber *first, NSNumber *second) {
    return @(first.integerValue + second.integerValue);
}];
  • 数量操作

1.Filter

// 过滤掉长度大于2的值
RACSignal *signalB = [signalA filter:^BOOL(NSString *value) {
    return value.length > 2;
}];

2.Ignore

// 忽略为1的值
RACSignal *signalC = [signalA ignore:@1];
// 效果等同于
RACSignal *signalB = [signalA filter:^BOOL(id value) {
    return ![@1 isEqual:value];
}];
// 忽略所有的值
- (RACSignal *)ignoreValues;
// 与上次的值不同时才发信号
- (RACSignal *)distinctUntilChanged;
// 只会收到一次信号
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];
[subject sendNext:@"HMJ"];
[subject sendNext:@"HMJ"];
[subject sendNext:@"HMJ"];

3.Take

// 取A的前两个值
RACSignal *signalB = [signalA take:2];

4.Take&Skip其它

// 取最后N次的值,会在信号完成后发送
- (RACSignal *)takeLast:(NSUInteger)count;
// 一直取值,直到block返回YES,如下例子
- (RACSignal *)takeUntilBlock:(BOOL (^)(id x))predicate;
// block返回YES才取值
- (RACSignal *)takeWhileBlock:(BOOL (^)(id x))predicate;
// 原理同上
- (RACSignal *)skipUntilBlock:(BOOL (^)(id x))predicate;
// 原理同上
- (RACSignal *)skipWhileBlock:(BOOL (^)(id x))predicate;

[[self.inputTextField.rac_textSignal takeUntilBlock:^BOOL(NSString *value) {
    return [value isEqualToString:@"stop"];
}] subscribeNext:^(NSString *value) {
    NSLog(@"current value is not `stop`: %@", value);
}];

5.StartWith

// 信号B以 Start 开头
RACSignal *signalB = [signalA startWith:@"Start"];

6.Repeat

// 不停地重复信号A
RACSignal *signalB = [signalA repeat];

7.Retry

// 如果返回错误则重新订阅,最多2次
RACSignal *signalB = [signalA retry:2];

8.副作用操作

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

// 把信号A的值收集为数组
RACSignal *signalB = [signalA collect];

10.Aggregate

// 汇聚,前一次的运算结果为running,下次的信号值为next
// 如果signalA的值依次为1、2、3、4、5,汇聚之后就是25
RACSignal *signalB = [signalA aggregateWithStart:@0 reduce:^id(NSNumber *running, NSNumber *next) {
    return @(running.integerValue + next.integerValue);
}];

11.Scan

// 使用Aggregate时,如果A不停止,B就永远不会返回值
// 而使用Scan就不会有这个弊端,每次运算结果都会返回
RACSignal *signalB = [signalA scanWithStart:@0 reduce:^id(NSNumber *running, NSNumber *next) {
    return @(running.integerValue + next.integerValue);
}];
  • 时间操作

1.Scheduler

// 比较准确的时间操作,可代替NSTimer
- (RACSignal *)interval:(NSTimeInterval)interval
            onScheduler:(RACScheduler *)scheduler;
- (RACSignal *)interval:(NSTimeInterval)interval
            onScheduler:(RACScheduler *)scheduler
             withLeeway:(NSTimeInterval)leeway;

2.Delay

// 准确度比 Scheduler 低
RACSignal *signalB = [signalA delay:1];

3.Throttle

// 阀门时间,设置时间内如果没有新的信号才返回值,否则就重新开始计时
RACSignal *signalB = [signalA throttle:1];
  • 组合操作

1.Concat

// 按信号顺序合并信号,第一个信号如果不停止,第二个信号就永远无法开始接收
// 可用于多网络请求时,返回数据需要按顺序拼接的场景
RACSignal *signalC = [signalA concat:signalB];

2.Merge

// 将多个信号合并成一个信号,按照发送值的顺序来合并,属于无序合并
RACSignal *signalC = [signalA merge:signalB];
RACSignal *signalC = [RACSignal merge:@[signalA, signalB]];
RACSignal *signalC = [RACSignal merge:RACTuplePack(signalA, signalB)];

3.Zip

// 合并两个信号的最新值为元组,只有两个信号都有新值时才会合并
RACSignal *signalC = [signalA zipWith:signalB];
RACSignal *signalC = [RACSignal zip:@[signalA, signalB]];
RACSignal *signalC = [RACSignal zip:RACTuplePack(signalA, signalB)];

4.CombineLatest

// 与zip相似,都是合并两个信号的值为元组,不过combineLatest只需一个信号值改变就能合并
RACSignal *signalC = [signalA combineLatestWith:signalB];
RACSignal *signalC = [RACSignal combineLatest:@[signalA, signalB]];

5.Sample

// B触发时才去获取A的实时值
RACSignal *signalC = [signalA sample:signalB];

6.TakeUntil

// 获取信号A,B开始就停止
RACSignal *signalC = [signalA takeUntil:signalB];

7.TakeUntilReplacement

// 获取信号A,B开始就停止获取A,转而获取B,直到B停止
RACSignal *signalC = [signalA takeUntilReplacement:signalB];
  • 订阅高阶信号

如果信号中包含了信号,它就是高阶信号。
下面代码中信号highOrderSignal中包含了信号,所以它是高阶信号。

RACSignal *signal = @[@1, @2, @3].rac_sequence.signal;
RACSignal *highOrderSignal = [signal map:^id(id value) {
    return [RACSignal return:value];
}];
[highOrderSignal subscribeNext:^(RACSignal *aSignal) {
    [aSignal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}];

将信号的值转变成信号叫做信号的升阶,将信号中的信号转变成值叫做信号的降阶。

  • 降阶操作

1.SwitchToLatest

switchToLatest能够对高阶信号进行降阶,它总是订阅最新的信号。

RACSignal *signalB = [signalA switchToLatest];

// 如下示例,订阅后最终收到的值为@"1"、@"11"、@"2"。
// 没有收到@"111"是因为它被发送时,sub1已经不是最新信号了
// 要注意,最新信号发生error会中断latest信号
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *sub1 = [RACSubject subject];
RACSubject *sub2 = [RACSubject subject];
RACSignal *latest = [signalOfSignals switchToLatest];
[latest subscribeNext:^(id x) {
    NSLog(@"***** %@",x);
}];
[signalOfSignals sendNext:sub1];
[sub1 sendNext:@"1"];
[sub1 sendNext:@"11"];
[signalOfSignals sendNext:sub2];
[sub2 sendNext:@"2"];
[sub1 sendNext:@"111"];

2.If/then/else

如果 signalA 的值为 true 取 signalTrue 的值,否则取 signalFalse 的值。

RACSignal *signalB = [RACSignal if:signalA
                              then:signalTrue
                              else:signalFalse];

3.Flatten
flatten进行的降阶与switchToLatest不同,它能够获取所有的值。

RACSignal *signalB = [signalA flatten];

方法- (RACSignal *)flatten:(NSUInteger)maxConcurrent中,maxConcurrent表示最大并发数。比如 flatten:2 表示当前最多只能对信号中的2个信号进行降阶操作,如果有新的信号产生,先进行等待,等到前面有信号结束之后才加入。

所以 Flatten 类似于 Merge,Flatten:1 类似于 Concat。

4.FlattenMap
高阶信号可以用flattenMap进行映射,在block中再对映射出的低阶信号进行映射,最后返回的信号就是降维后的信号。

  • 冷信号与热信号

热信号:
热信号是主动的,即使你没有订阅事件,它仍然会不断推送。
热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。

冷信号:
冷信号是被动的,只有当你订阅的时候,它才会发送消息。
冷信号只能一对一,当有新的订阅者时,消息会重新完整发送。

在 RAC 中,RACSubject及其子类是热信号,RACSignalRACSubject以外的信号是冷信号。

1.热信号

RACSubject需要先订阅再发送:

RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id x) {
    //
} error:^(NSError *error) {
    //
} completed:^{
    //
}];

[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];

[subject sendCompleted];

RACReplaySubject可先发送再订阅:

RACReplaySubject *subject = [RACReplaySubject replaySubjectWithCapacity:1];

[subject sendNext:@1];
[subject sendNext:@2];
[subject sendCompleted];

[subject subscribeNext:^(id x) {
    //
}];

RACReplaySubject是具备快速回播的信号,同时它还能控制“历史值”的数量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335