[TOC]
简介
函数响应式编程(Functional Reactive Programming:FRP),ReactiveCocoa 版本 2.5
冷热信号
- 冷信号
只有当你订阅的时候,它才会发布消息,
一对一,当有不同的订阅者,消息是重新完整发送。
- 热信号
尽管你并没有订阅事件,但是它会时刻推送,类似“直播”,错过了就不再处理。
可以有多个订阅者,是一对多
RACSignal
与RACSubject
的区别
RACSignal
是冷信号RACSubject
是热信号
如下图
signal
subject
replaySubject
Subject可以附加行为,例如
RACReplaySubject
具备为未来订阅者缓冲事件的能力。(这一点与冷信号类似,即使是在数据发送之后才订阅的,依然会收到全部消息)
冷信号示例:延时订阅,依然能收到所有信号数据
- (void)test1 {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendCompleted];
return nil;
}];
NSLog(@"Signal was created.");
[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber1 recveive: %@", x);
}];
}];
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[signal subscribeNext:^(id x) {
NSLog(@"Subscriber2 recveive: %@", x);
}];
}];
}
热信号示例:错过了订阅时机,就收不到信号数据,类似于直播
冷信号会收到全部的数据,即使是在数据发送之后才订阅的
- (void)test2 {
RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{
[subscriber sendNext:@1];
}];
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
[subscriber sendNext:@2];
}];
[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{
[subscriber sendNext:@3];
[subscriber sendCompleted];
}];
return nil;
}] publish];
[connection connect];
NSLog(@"Signal was created.");
[[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{
[connection.signal subscribeNext:^(id x) {
NSLog(@"Subscriber1 recveive: %@", x);
}];
}];
[[RACScheduler mainThreadScheduler] afterDelay:2.1 schedule:^{
[connection.signal subscribeNext:^(id x) {
NSLog(@"Subscriber2 recveive: %@", x);
}];
}];
}
SideEffect示例:多次订阅导致信号block多次执行
- (void)test3 {
// 多次订阅会多次执行
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"开始请求网络数据");
[RACScheduler.mainThreadScheduler afterDelay:1 schedule:^{
[subscriber sendNext:@"1"];
}];
return nil;
}];
// 【请求数据次数 +1】
[requestSignal subscribeNext:^(id x) {
NSLog(@"订阅者1");
}];
// 【请求数据次数 +1】
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者2");
}];
// 将信号转换为内容为2的信号
RACSignal *signal1 = [requestSignal flattenMap:^RACStream *(id value) {
return [RACSignal return:@"2"];
}];
// 将signal1信号所有错误信息转换为字符串@"Error"
[signal1 catchTo:[RACSignal return:@"Error"]];
// 在没有获取值之前以字符串@"Loading..."占位
[signal1 startWith:@"Loading..."];
// 将信号进行绑定
// 【请求数据次数 +1】
RAC(self.acountField, text) = signal1;
// 订阅多个信号的任何错误,并且弹出UIAlertView
// 【请求数据次数 +2】
[[RACSignal merge:@[requestSignal, signal1]] subscribeError:^(NSError *error) {
NSLog(@"发生错误");
}];
}
解决方式一:使用 RACMulticastConnection
把冷信号转化为热信号
- (void)test4 {
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"开始请求网络数据");
[RACScheduler.mainThreadScheduler afterDelay:1 schedule:^{
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}];
return nil;
}];
RACMulticastConnection *connection = [requestSignal multicast:[RACSubject subject]];
// RACMulticastConnection *connection = [requestSignal multicast:[RACReplaySubject subject]];
[connection connect];
[connection.signal subscribeNext:^(id x) {
NSLog(@"订阅者1:%@", x);
}];
[connection.signal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者2:%@", x);
}];
[RACScheduler.mainThreadScheduler afterDelay:2 schedule:^{
[connection.signal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者3:%@", x);
}];
}];
}
使用RACSubject
时
RACMulticastConnection *connection = [requestSignal multicast:[RACSubject subject]];
使用RACReplaySubject
时
RACMulticastConnection *connection = [requestSignal multicast:[RACReplaySubject subject]];
解决方式二:使用 replayLazily
把冷信号转化为热信号
- (void)test5 {
RACSignal *requestSignal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"开始请求网络数据");
[RACScheduler.mainThreadScheduler afterDelay:1 schedule:^{
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}];
return nil;
}] replayLazily]; // modify here!!
[requestSignal subscribeNext:^(id x) {
NSLog(@"订阅者1:%@", x);
}];
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者2:%@", x);
}];
[RACScheduler.mainThreadScheduler afterDelay:2 schedule:^{
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者3:%@", x);
}];
}];
}
使用RACCommand
把冷信号转化为热信号
- (void)test6 {
RACCommand *requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"开始请求网络数据");
[RACScheduler.mainThreadScheduler afterDelay:1 schedule:^{
[subscriber sendNext:@"1"];
[subscriber sendCompleted];
}];
return nil;
}];
}];
RACSignal *requestSignal = [requestCommand execute:nil];
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者1:%@", x);
}];
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者2:%@", x);
}];
[RACScheduler.mainThreadScheduler afterDelay:2 schedule:^{
[requestSignal subscribeNext:^(NSArray *x) {
NSLog(@"订阅者3:%@", x);
}];
}];
}
总结
RACMulticastConnection
与RACSubject
结合使用时是直播的热信号
RACCommand
、replayLazily
、RACReplaySubject
都是类似于冷信号的情况,不管何时订阅,都会收到所有数据
ReactiveCocoa中潜在的内存泄漏与解决方案
RACObserve
中潜在使用了self,要注意循环引用RACSubject
中如果没有调用sendCompleted
,调用map等操作将造成内存泄漏(循环引用)。RACSignal不会有这个问题
代码参见https://github.com/action456789/ReactiveCocoaDemo
参考:http://tech.meituan.com/tag/ReactiveCocoa