[TOC]
簡介
函數響應式編程(Functional Reactive Programming:FRP),ReactiveCocoa 版本 2.5
冷熱信號
- 冷信號
只有當你訂閱的時候,它才會發布消息,
一對一,當有不同的訂閱者,消息是重新完整發送。
- 熱信號
盡管你并沒有訂閱事件,但是它會時刻推送,類似“直播”,錯過了就不再處理。
可以有多個訂閱者,是一對多
RACSignal
與RACSubject
的區別
RACSignal
是冷信號RACSubject
是熱信號
如下圖
signal
image.png
subject
image.png
replaySubject
image.png
Subject可以附加行為,例如
RACReplaySubject
具備為未來訂閱者緩沖事件的能力。(這一點與冷信號類似,即使是在數據發送之后才訂閱的,依然會收到全部消息)
冷信號示例:延時訂閱,依然能收到所有信號數據
- (void)test1 {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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);
}];
}];
}
image.png
熱信號示例:錯過了訂閱時機,就收不到信號數據,類似于直播
冷信號會收到全部的數據,即使是在數據發送之后才訂閱的
- (void)test2 {
RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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);
}];
}];
}
image.png
SideEffect示例:多次訂閱導致信號block多次執行
- (void)test3 {
// 多次訂閱會多次執行
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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(@"發生錯誤");
}];
}
image.png
解決方式一:使用 RACMulticastConnection
把冷信號轉化為熱信號
- (void)test4 {
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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]];
image.png
使用RACReplaySubject
時
RACMulticastConnection *connection = [requestSignal multicast:[RACReplaySubject subject]];
image.png
解決方式二:使用 replayLazily
把冷信號轉化為熱信號
- (void)test5 {
RACSignal *requestSignal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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);
}];
}];
}
image.png
使用RACCommand
把冷信號轉化為熱信號
- (void)test6 {
RACCommand *requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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);
}];
}];
}
image.png
總結
RACMulticastConnection
與RACSubject
結合使用時是直播的熱信號
RACCommand
、replayLazily
、RACReplaySubject
都是類似于冷信號的情況,不管何時訂閱,都會收到所有數據
ReactiveCocoa中潛在的內存泄漏與解決方案
RACObserve
中潛在使用了self,要注意循環引用RACSubject
中如果沒有調用sendCompleted
,調用map等操作將造成內存泄漏(循環引用)。RACSignal不會有這個問題