學習 RAC 我們首先要了解 RAC 都有哪些類
- RACSignal 信號類
- RACSubject 信號提供者,自己可以充當信號,又能發送信號.使用場景:通常用來代替代理,有了它,就不必要定義代理了。
- RACSequence 信號的集合
- RACMulticastConnection 用於當一個信號,被多次訂閱時,爲了保證創建信號時,避免多次調用創建信號中的block,造成副作用,可以使用這個類處理。
- RACCommand RAC中用於處理事件的類,可以把事件如何處理,事件中的數據如何傳遞,包裝到這個類中,他可以很方便的監控事件的執行過程。
RACSignal:
RACSiganl:信號類,一般表示將來有數據傳遞,只要有數據改變,信號內部接收到數據,就會馬上發出數據。
信號類(RACSiganl),只是表示當數據改變時,信號內部會發出數據,它本身不具備發送信號的能力,而是交給內部一個訂閱者去發出。
默認一個信號都是冷信號,也就是值改變了,也不會觸發,只有訂閱了這個信號,這個信號纔會變爲熱信號,值改變了纔會觸發。
如何訂閱信號:調用信號RACSignal的subscribeNext就能訂閱。
//1.創建信號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//3.發送信號
[subscriber sendNext:@"發送信號"];
/**
如果不在發送數據,最好發送信號完成,內部會自動調用[RACDisposable disposable]取消訂閱信號
*/
[subscriber sendCompleted];
//取消訂閱方法
return [RACDisposable disposableWithBlock:^{
//block調用時刻:當信號發送完成或者發送錯誤,就會自動執行這個block,取消訂閱信號
// 執行完Block後,當前信號就不在被訂閱了。
NSLog(@"信號銷燬了");
}];
}];
//2.訂閱信號
[signal subscribeNext:^(id x) {
NSLog(@"訂閱信號:%@",x);
}];
RACSignal底層實現
1.創建信號,首先把didSubscribe保存到信號中,還不會觸發。
2.當信號被訂閱,也就是調用signal的subscribeNext:nextBlock
2.1 subscribeNext內部會調用siganl的didSubscribe
2.2 subscribeNext內部會創建訂閱者subscriber,並且把nextBlock保存到subscriber中
3.siganl的didSubscribe中調用[subscriber sendNext:@"發送信號"];
3.1 sendNext底層其實就是執行subscriber的nextBlock
注意:
當一個信號被全局變量保存值的時候,我們要手動取消訂閱
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
/**
有一個全局變量保存值就不會走下面取消訂閱方法
*/
_subscriber = subscriber;
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"走銷燬這個方法了");
}];
}];
RACDisposable 取消訂閱
//訂閱信號返回RACDisposable
RACDisposable *disposable = [signal1 subscribeNext:^(id x) {
NSLog(@"接收打印的值:%@",x);
}];
// 默認一個信號發送數據完畢們就會主動取消訂閱.
// 只要訂閱者在,就不會自動取消信號訂閱
// 手動取消訂閱者
[disposable dispose];
RACSubscriber:
表示訂閱者的意思,用於發送信號,這是一個協議,不是一個類,只要遵守這個協議,並且實現方法才能成爲訂閱者。通過create創建的信號,都有一個訂閱者,幫助他發送數據。
RACDisposable:用於取消訂閱或者清理資源,當信號發送完成或者發送錯誤的時候,就會自動觸發它。
使用場景:不想監聽某個信號時,可以通過它主動取消訂閱信號。
RACSubject:RACSubject:信號提供者,自己可以充當信號,又能發送信號。
使用場景:通常用來代替代理,有了它,就不必要定義代理了。
RACReplaySubject:重複提供信號類,RACSubject的子類。
RACReplaySubject與RACSubject區別:
RACReplaySubject可以先發送信號,在訂閱信號,RACSubject就不可以。
使用場景一:如果一個信號每被訂閱一次,就需要把之前的值重複發送一遍,使用重複提供信號類。
使用場景二:可以設置capacity數量來限制緩存的value的數量,即只緩充最新的幾個值。
// RACSubject使用步驟
// 1.創建信號 [RACSubject subject],跟RACSiganl不一樣,創建信號時沒有block。
// 2.訂閱信號 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.發送信號 sendNext:(id)value
//1.創建信號
RACSubject *subject = [RACSubject subject];
//2.訂閱信號
[subject subscribeNext:^(id x) {
//當信號發出新值,就會調用.
NSLog(@"第一個訂閱者%@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"第二個訂閱者%@",x);
}];
//3.發送信號
[subject sendNext:@"123456"];
//4.發送信號完成,內部會自動取消訂閱者
[subject sendCompleted];
//輸出:
// 第一個訂閱者123456
// 第二個訂閱者123456
RACReplaySubject
RACReplaySubject使用步驟:
1.創建信號 [RACSubject subject],跟RACSiganl不一樣,創建信號時沒有block。
2.可以先訂閱信號,也可以先發送信號。
2.1 訂閱信號 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
2.2 發送信號 sendNext:(id)value
RACReplaySubject:底層實現和RACSubject不一樣。
1.調用sendNext發送信號,把值保存起來,然後遍歷剛剛保存的所有訂閱者,一個一個調用訂閱者的nextBlock。
2.調用subscribeNext訂閱信號,遍歷保存的所有值,一個一個調用訂閱者的nextBlock
//1. RACReplaySubject *replaySubject = [RACReplaySubject subject];
//2.發送信號 [replaySubject sendNext:@"1"];
[replaySubject sendNext:@"2"];
//3.訂閱信號 [replaySubject subscribeNext:^(id x) {
NSLog(@"第一個訂閱者接收到的數據%@",x);
}];
[replaySubject subscribeNext:^(id x) {
NSLog(@"第二個訂閱者接收到的數據%@",x);
}];
//輸出:
//第一個訂閱者接收到的數據
1 //第一個訂閱者接收到的數據
2 //第二個訂閱者接收到的數據
1 //第二個訂閱者接收到的數據2
RAC集合
RACTuple:元組類,類似NSArray,用來包裝值. /**
RACTuple:元組類,類似NSArray,用來包裝值.
*/
//元組
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"123",@"345",@1]];
NSString *first = tuple[0];
NSLog(@"%@",first);
//輸出:
//123
數組
NSArray *arr = @[@"213",@"321",@1];
//RAC 集合
// RACSequence *sequence = arr.rac_sequence;
// 把集合轉換成信號
// RACSignal *signal = sequence.signal;
// 訂閱集合信號,內部會自動遍歷所有的元素發出來
// [signal subscribeNext:^(id x) {
// NSLog(@"遍歷數組%@",x);
// }];
//輸出:
/**
遍歷數組213
遍歷數組321
遍歷數組1
*/
//高級寫法
[arr.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"高級寫法遍歷數組打印%@",x);
}];
//輸出:
/**
高級寫法遍歷數組打印213
高級寫法遍歷數組打印321
高級寫法遍歷數組打印1
*/
字典
NSDictionary *dict = @{@"sex":@"女",@"name":@"蒼老師",@"age":@18};
//轉換成集合
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// NSString *key = x[0];
// NSString *value = x[1];
// NSLog(@"%@ %@",key,value);
// RACTupleUnpack:用來解析元組
// 宏裏面的參數,傳需要解析出來的變量名
//= 右邊,放需要解析的元組
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"%@ %@",key,value);
}];
//輸出:
/**
sex 女
name 蒼老師
age 18
*/
字典轉模型NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
// NSMutableArray *arr = [NSMutableArray array];
// // rac_sequence注意點:調用subscribeNext,並不會馬上執行nextBlock,而是會等一會。
// [dictArr.rac_sequence.signal subscribeNext:^(NSDictionary *x) {
// Flag *flag = [Flag flagWithDict:x];
// [arr addObject:flag];
// }];
//高級用法
// map:映射的意思,目的:把原始值value映射成一個新值
// array: 把集合轉換成數組
// 底層實現:當信號被訂閱,會遍歷集合中的原始值,映射成新值,並且保存到新的數組裏。
NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
return [Flag flagWithDict:value];
}] array];
NSLog(@"%@",arr);
RACMulticastConnection:
RACMulticastConnection:用於當一個信號,被多次訂閱時,爲了保證創建信號時,避免多次調用創建信號中的block,造成副作用,可以使用這個類處理。
使用注意:RACMulticastConnection通過RACSignal的-publish或者-muticast:方法創建.
RACMulticastConnection簡單使用:
RACMulticastConnection使用步驟:
1.創建信號 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
2.創建連接 RACMulticastConnection *connect = [signal publish];
3.訂閱信號,注意:訂閱的不在是之前的信號,而是連接的信號。 [connect.signal subscribeNext:nextBlock]
4.連接 [connect connect]
RACMulticastConnection底層原理:
1.創建connect,connect.sourceSignal -> RACSignal(原始信號) connect.signal -> RACSubject
2.訂閱connect.signal,會調用RACSubject的subscribeNext,創建訂閱者,而且把訂閱者保存起來,不會執行block。
3.[connect connect]內部會訂閱RACSignal(原始信號),並且訂閱者是RACSubject
3.1.訂閱原始信號,就會調用原始信號中的didSubscribe
3.2 didSubscribe,拿到訂閱者調用sendNext,其實是調用RACSubject的sendNext
4.RACSubject的sendNext,會遍歷RACSubject所有訂閱者發送信號。
4.1 因爲剛剛第二步,都是在訂閱RACSubject,因此會拿到第二步所有的訂閱者,調用他們的nextBlock
需求:假設在一個信號中發送請求,每次訂閱一次都會發送請求,這樣就會導致多次請求。
解決:使用RACMulticastConnection就能解決.
//1.創建信號
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"發送請求");
[subscriber sendNext:@"1"];
return [RACDisposable disposableWithBlock:^{
}];
}];
//2.訂閱信號
[signal subscribeNext:^(id x) {
NSLog(@"接收數據");
}];
[signal subscribeNext:^(id x) {
NSLog(@"接收數據");
}];
// 3.運行結果,會執行兩遍發送請求,也就是每次訂閱都會發送一次請求
// RACMulticastConnection:解決重複請求問題
// 1.創建信號
RACSignal *connectionSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"發送請求--");
[subscriber sendNext:@1];
return [RACDisposable disposableWithBlock:^{
}];
}];
//2.創建連接
RACMulticastConnection *connect = [connectionSignal publish];
//3.訂閱信號
// 注意:訂閱信號,也不能激活信號,只是保存訂閱者到數組,必須通過連接,當調用連接,就會一次性調用所有訂閱者的sendNext:
[connect.signal subscribeNext:^(id x) {
NSLog(@"訂閱者一信號--");
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"訂閱者二信號--");
}];
//4.連接,激活信號
[connect connect];
//輸出:
/**
發送請求--
訂閱者一信號--
訂閱者二信號--
*/
RACCommand
RACCommand:RAC中用於處理事件的類,可以把事件如何處理,事件中的數據如何傳遞,包裝到這個類中,他可以很方便的監控事件的執行過程。
使用場景:監聽按鈕點擊,網絡請求
一、RACCommand使用步驟:
1.創建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
2.在signalBlock中,創建RACSignal,並且作爲signalBlock的返回值
3.執行命令 - (RACSignal *)execute:(id)input
二、RACCommand使用注意:
1.signalBlock必須要返回一個信號,不能傳nil.
2.如果不想要傳遞信號,直接創建空的信號[RACSignal empty];
3.RACCommand中信號如果數據傳遞完,必須調用[subscriber sendCompleted],這時命令纔會執行完畢,否則永遠處於執行中。
三、RACCommand設計思想:內部signalBlock爲什麼要返回一個信號,這個信號有什麼用。
1.在RAC開發中,通常會把網絡請求封裝到RACCommand,直接執行某個RACCommand就能發送請求。
2.當RACCommand內部請求到數據的時候,需要把請求的數據傳遞給外界,這時候就需要通過signalBlock返回的信號傳遞了。
四、如何拿到RACCommand中返回信號發出的數據。
1.RACCommand有個執行信號源executionSignals,這個是signal of signals(信號的信號),意思是信號發出的數據是信號,不是普通的類型。
2.訂閱executionSignals就能拿到RACCommand中返回的信號,然後訂閱signalBlock返回的信號,就能獲取發出的值。
五、監聽當前命令是否正在執行executing
六、使用場景,監聽按鈕點擊,網絡請求
//1.創建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"執行命令");
//創建空信號,必須返回信號
//return [RACSignal empty];
//2.創建信號,用來傳遞數據
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"請求數據"];
//注意:數據傳遞完,最好調用sendCompleted,這時命令才執行完畢。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"數據銷燬");
}];
}];
}];
//強引用命令,不要被銷燬,否則接收不到數據
_command = command;
//監聽事件有咩有完成
[command.executing subscribeNext:^(id x) {
if ([x boolValue] == YES) { // 當前正在執行
NSLog(@"當前正在執行");
}else{
// 執行完成/沒有執行
NSLog(@"執行完成/沒有執行");
}
}];
//執行命令
[self.command execute:@1];
RAC高級用法
//創建信號中信號
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [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:@1];
[signalB sendNext:@"BB"];
[signalA sendNext:@"11"];
RACCommand:處理事件
RACCommand:不能返回一個空的信號
// 1.創建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
// input:執行命令傳入參數
// Block調用:執行命令的時候就會調用
NSLog(@"%@",input);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 發送數據
[subscriber sendNext:@"執行命令產生的數據"];
return nil;
}];
}];
// 如何拿到執行命令中產生的數據
// 訂閱命令內部的信號
// 1.方式一:直接訂閱執行命令返回的信號
// 2.方式二:
// 2.執行命令
RACSignal *signal = [command execute:@1];
// 3.訂閱信號
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
RAC 中常見的宏:
使用宏定義要單獨導入 #import <RACEXTScope.h>
一. 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);
}];
當RACObserve放在block裏面使用時一定要加上weakify,不管裏面有沒有使用到self;否則會內存泄漏,因爲RACObserve宏裏面就有一個self
@weakify(self);
RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) {
Avoids a retain cycle because of RACObserve implicitly referencing self
@strongify(self);
return RACObserve(arrayController, items);
}];
三. @weakify(Obj)和@strongify(Obj),一般兩個都是配套使用,在主頭文件(ReactiveCocoa.h)中並沒有導入,需要自己手動導入,RACEXTScope.h纔可以使用。但是每次導入都非常麻煩,只需要在主頭文件自己導入就好了
四. RACTuplePack:把數據包裝成RACTuple(元組類)
把參數中的數據包裝成元組
RACTuple *tuple = RACTuplePack(@10,@20);
五.
RACTupleUnpack:把RACTuple(元組類)解包成對應的數據
// 把參數中的數據包裝成元組
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元組,會把元組的值,按順序給參數裏面的變量賦值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
RAC常用用法:
1.監聽按鈕的點擊事件:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(100, 100, 80, 40);
[button setTitle:@"點擊事件" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor redColor]];
[self.view addSubview:button];
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按鈕被點擊了");
}];
2.代理的使用:
@interface RedView : UIView
//* */
@property (nonatomic, strong) RACSubject *btnClickSignal;
@end
#import "RedView.h"
@implementation RedView
- (void)awakeFromNib {
[super awakeFromNib];
}
//懶加載信號
- (RACSubject *)btnClickSignal {
if (_btnClickSignal == nil) {
_btnClickSignal = [RACSubject subject];
}
return _btnClickSignal;
}
- (IBAction)btnClick:(UIButton *)sender {
[self.btnClickSignal sendNext:@"按鈕被點擊了"];
}
@end
// 只要傳值,就必須使用RACSubject
RedView *redView = [[[NSBundle mainBundle] loadNibNamed:@"RedView" owner:nil options:nil] lastObject];
redView.frame = CGRectMake(0, 140, self.view.bounds.size.width, 200);
[self.view addSubview:redView];
[redView.btnClickSignal subscribeNext:^(id x) {
NSLog(@"---點擊按鈕了,觸發了事件");
}];
// 把控制器調用didReceiveMemoryWarning轉換成信號
//rac_signalForSelector:監聽某對象有沒有調用某方法
[[self rac_signalForSelector:@selector(didReceiveMemoryWarning)] subscribeNext:^(id x) {
NSLog(@"控制器調用didReceiveMemoryWarning");
}];
3.KVO
把監聽redV的center屬性改變轉換成信號,只要值改變就會發送信號
// observer:可以傳入nil
[[redView rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"center:%@",x);
}];
4.代替通知
UITextField *textfield = [[UITextField alloc] initWithFrame:CGRectMake(100, 340, 150, 40)];
textfield.placeholder = @"監聽鍵盤的彈起";
textfield.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview:textfield];
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"監聽鍵盤的高度:%@",x);
}];
5.監聽文字的改變
1 2 3 4 |
|
6.處理多個請求,都返回結果的時候,統一做處理.
RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 發送請求1
[subscriber sendNext:@"發送請求1"];
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 發送請求2
[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);
}
ReactiveObjC常見操作方法介紹:
1.1 v操作須知
所有的信號(RACSignal)都可以進行操作處理,因爲所有操作方法都定義在RACStream.h中,而RACSignal繼承RACStream。
1.2 ReactiveObjC操作思想
運用的是Hook(鉤子)思想,Hook是一種用於改變API(應用程序編程接口:方法)執行結果的技術.
Hook用處:截獲API調用的技術。
Hook原理:在每次調用一個API返回結果之前,先執行你自己的方法,改變結果的輸出。
RAC開發方式:RAC中核心開發方式,也是綁定,之前的開發方式是賦值,而用RAC開發,應該把重心放在綁定,也就是可以在創建一個對象的時候,就綁定好以後想要做的事情,而不是等賦值之後在去做事情。
列如:把數據展示到控件上,之前都是重寫控件的setModel方法,用RAC就可以在一開始創建控件的時候,就綁定好數據。
1.3 ReactiveObjC核心方法bind
ReactiveObjC操作的核心方法是bind(綁定),給RAC中的信號進行綁定,只要信號一發送數據,就能監聽到,從而把發送數據改成自己想要的數據。
在開發中很少使用bind方法,bind屬於RAC中的底層方法,RAC已經封裝了很多好用的其他方法,底層都是調用bind,用法比bind簡單.
bind方法簡單介紹和使用。
// 假設想監聽文本框的內容,並且在每次輸出結果的時候,都在文本框的內容拼接一段文字“輸出:”
// 方式一:在返回結果後,拼接。
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"輸出:%@",x);
}];
// 方式二:在返回結果前,拼接,使用RAC中bind方法做處理。
// bind方法參數:需要傳入一個返回值是RACStreamBindBlock的block參數
// RACStreamBindBlock是一個block的類型,返回值是信號,參數(value,stop),因此參數的block返回值也是一個block。
// RACStreamBindBlock:
// 參數一(value):表示接收到信號的原始值,還沒做處理
// 參數二(*stop):用來控制綁定Block,如果*stop = yes,那麼就會結束綁定。
// 返回值:信號,做好處理,在通過這個信號返回出去,一般使用RACReturnSignal,需要手動導入頭文件RACReturnSignal.h。
// bind方法使用步驟:
// 1.傳入一個返回值RACStreamBindBlock的block。
// 2.描述一個RACStreamBindBlock類型的bindBlock作爲block的返回值。
// 3.描述一個返回結果的信號,作爲bindBlock的返回值。
// 注意:在bindBlock中做信號結果的處理。
// 底層實現:
// 1.源信號調用bind,會重新創建一個綁定信號。
// 2.當綁定信號被訂閱,就會調用綁定信號中的didSubscribe,生成一個bindingBlock。
// 3.當源信號有內容發出,就會把內容傳遞到bindingBlock處理,調用bindingBlock(value,stop)
// 4.調用bindingBlock(value,stop),會返回一個內容處理完成的信號(RACReturnSignal)。
// 5.訂閱RACReturnSignal,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
// 注意:不同訂閱者,保存不同的nextBlock,看源碼的時候,一定要看清楚訂閱者是哪個。
// 這裏需要手動導入#import <ReactiveCocoa/RACReturnSignal.h>,才能使用RACReturnSignal。
[[_textField.rac_textSignal bind:^RACStreamBindBlock{
// 什麼時候調用:
// block作用:表示綁定了一個信號.
return ^RACStream *(id value, BOOL *stop){
// 什麼時候調用block:當信號有新的值發出,就會來到這個block。
// block作用:做返回值的處理
// 做好處理,通過信號返回出去.
return [RACReturnSignal return:[NSString stringWithFormat:@"輸出:%@",value]];
};
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
flattenMap
flattenMap使用步驟:
1.傳入一個block,block類型是返回值RACStream,參數value
2.參數value就是源信號的內容,拿到源信號的內容做處理
3.包裝成RACReturnSignal信號,返回出去。
flattenMap底層實現:
0.flattenMap內部調用bind方法實現的,flattenMap中block的返回值,會作爲bind中bindBlock的返回值。
1.當訂閱綁定信號,就會生成bindBlock。
2.當源信號發送內容,就會調用bindBlock(value, *stop)
3.調用bindBlock,內部就會調用flattenMap的block,flattenMap的block作用:就是把處理好的數據包裝成信號。
4.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
5.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
[[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
// block什麼時候 : 源信號發出的時候,就會調用這個block。
// block作用 : 改變源信號的內容。
// 返回值:綁定信號的內容.
return [RACReturnSignal return:[NSString stringWithFormat:@"輸出:%@",value]];
}] subscribeNext:^(id x) {
// 訂閱綁定信號,每當源信號發送內容,做完處理,就會調用這個block。
NSLog(@"輸出:flattenMap%@",x);
}];
map
Map使用步驟:
1.傳入一個block,類型是返回對象,參數是value
2.value就是源信號的內容,直接拿到源信號的內容做處理
3.把處理好的內容,直接返回就好了,不用包裝成信號,返回的值,就是映射的值。
Map底層實現:
0.Map底層其實是調用flatternMap,Map中block中的返回的值會作爲flatternMap中block中的值。
1.當訂閱綁定信號,就會生成bindBlock。
3.當源信號發送內容,就會調用bindBlock(value, *stop)
4.調用bindBlock,內部就會調用flattenMap的block
5.flattenMap的block內部會調用Map中的block,把Map中的block返回的內容包裝成返回的信號。
5.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
6.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
[[_textField.rac_textSignal map:^id(id value) {
// 當源信號發出,就會調用這個block,修改源信號的內容
// 返回值:就是處理完源信號的內容。
return [NSString stringWithFormat:@"輸出:%@",value];
}] subscribeNext:^(id x) {
NSLog(@"map輸出%@",x);
}];
FlatternMap和Map的區別
1.FlatternMap中的Block返回信號。
2.Map中的Block返回對象。
3.開發中,如果信號發出的值不是信號,映射一般使用Map
4.開發中,如果信號發出的值是信號,映射一般使用FlatternMap。
總結:signalOfsignals用FlatternMap。
//創建信號中的信號
RACSubject *signalOfsignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[[signalOfsignals flattenMap:^RACStream *(id value) {
//當signalOfsignals的signals發出信號纔會調用
return value;
}] subscribeNext:^(id x) {
// 只有signalOfsignals的signal發出信號纔會調用,因爲內部訂閱了bindBlock中返回的信號,也就是flattenMap返回的信號。
// 也就是flattenMap返回的信號發出內容,纔會調用。
NSLog(@"%@aaa",x);
}];
// 信號的信號發送信號
[signalOfsignals sendNext:signal];
// 信號發送內容
[signal sendNext:@1];
RAC操作之組合
1.concat
按一定順序拼接信號,當多個信號發出的時候,有順序的接收信號
//一.concat:按一定順序拼接信號,當多個信號發出的時候,有順序的接收信號。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@2];
[subscriber sendCompleted];
return nil;
}];
// 把signalA拼接到signalB後,signalA發送完成,signalB纔會被激活。
RACSignal *concatSignal = [signalA concat:signalB];
// 以後只需要面對拼接信號開發。
// 訂閱拼接的信號,不需要單獨訂閱signalA,signalB
// 內部會自動訂閱。
// 注意:第一個信號必須發送完成,第二個信號纔會被激活
[concatSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// concat底層實現:
// 1.當拼接信號被訂閱,就會調用拼接信號的didSubscribe
// 2.didSubscribe中,會先訂閱第一個源信號(signalA)
// 3.會執行第一個源信號(signalA)的didSubscribe
// 4.第一個源信號(signalA)didSubscribe中發送值,就會調用第一個源信號(signalA)訂閱者的nextBlock,通過拼接信號的訂閱者把值發送出來.
// 5.第一個源信號(signalA)didSubscribe中發送完成,就會調用第一個源信號(signalA)訂閱者的completedBlock,訂閱第二個源信號(signalB)這時候才激活(signalB)。
// 6.訂閱第二個源信號(signalB),執行第二個源信號(signalB)的didSubscribe
// 7.第二個源信號(signalA)didSubscribe中發送值,就會通過拼接信號的訂閱者把值發送出來.
2.then
用於連接兩個信號,當第一個信號完成,纔會連接then返回的信號
then:用於連接兩個信號,當第一個信號完成,纔會連接then返回的信號
注意使用then,之前信號的值會被忽略掉.
底層實現:1、先過濾掉之前的信號發出的值。2.使用concat連接then返回的信號
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@2];
return nil;
}];
}] subscribeNext:^(id x) {
// 只能接收到第二個信號的值,也就是then返回信號的值
NSLog(@"%@",x);
}];
3merge
把多個信號合併爲一個信號,任何一個信號有新值的時候就會調用.
merge: 把多個信號合併成一個信號
創建多個信號
RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@3];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信號釋放了");
}];
}];
RACSignal *signalD = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@4];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信號釋放了");
}];
}];
//合併信號.任何一個信號發送數據,都能監聽到
RACSignal *mergeSignal = [signalC merge:signalD];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"合併信號,任何一個數據發送數據,都能監聽到%@",x);
}];
// 底層實現:
// 1.合併信號被訂閱的時候,就會遍歷所有信號,並且發出這些信號。
// 2.每發出一個信號,這個信號就會被訂閱
// 3.也就是合併信號一被訂閱,就會訂閱裏面所有的信號。
// 4.只要有一個信號被髮出就會被監聽。
4.zipWith
把兩個信號壓縮成一個信號,只有當兩個信號同時發出信號內容時,並且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的next事件。
RACSignal *signalE = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@5];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *signalF = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@6];
return [RACDisposable disposableWithBlock:^{
}];
}];
// 壓縮信號A,信號B
RACSignal *zipSignal = [signalE zipWith:signalF];
[zipSignal subscribeNext:^(id x) {
NSLog(@"壓縮的信號%@",x);
}];
// 底層實現:
// 1.定義壓縮信號,內部就會自動訂閱signalA,signalB
// 2.每當signalA或者signalB發出信號,就會判斷signalA,signalB有沒有發出個信號,有就會把最近發出的信號都包裝成元組發出。
5.combineLatest
將多個信號合併起來,並且拿到各個信號的最新的值,必須每個合併的signal至少都有過一次sendNext,纔會觸發合併的信號.
RACSignal *signalG = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@5];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *signalH = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@6];
return [RACDisposable disposableWithBlock:^{
}];
}];
// 把兩個信號組合成一個信號,跟zip一樣,沒什麼區別
RACSignal *combineSignal = [signalG combineLatestWith:signalH];
[combineSignal subscribeNext:^(id x) {
NSLog(@"把兩個信號組合成一個信號%@",x);
}];
// 底層實現:
// 1.當組合信號被訂閱,內部會自動訂閱signalA,signalB,必須兩個信號都發出內容,纔會被觸發。
// 2.並且把兩個信號組合成元組發出。
6.reduce聚合
用於信號發出的內容是元組,把信號發出元組的值聚合成一個值
RACSignal *signalL = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@5];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *signalM = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@6];
return [RACDisposable disposableWithBlock:^{
}];
}];
RACSignal *reduceSignal = [RACSignal combineLatest:@[signalL,signalM] reduce:^id(NSNumber *num1, NSNumber *num2){
return [NSString stringWithFormat:@"reduce聚合%@ %@",num1,num2];
}];
[reduceSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// // 底層實現:
// 1.訂閱聚合信號,每次有內容發出,就會執行reduceblcok,把信號內容轉換成reduceblcok返回的值。
ReactiveCocoa常見操作方法介紹:
- filter
- ignore
- ignoreValues
- takeUntilBlock
- distinctUntilChanged
- take
- takeLast
- takeUntil
- skip
- switchToLatest
filter: 過濾信號,使用它可以獲取滿足條件的信號.
//filter 過濾
//每次信號發出,會先執行過濾條件判斷
[_textField.rac_textSignal filter:^BOOL(NSString *value) {
return value.length > 3;
}];
ignore:忽略完某些值的信號.
// 內部調用filter過濾,忽略掉ignore的值
[[_textField.rac_textSignal ignore:@"1"] subscribeNext:^(id x) {
NSLog(@"ignore%@",x);
}];
ignoreValues 這個比較極端,忽略所有值,只關心Signal結束,也就是隻取Comletion和Error兩個消息,中間所有值都丟棄
RACSignal *signal=[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"3"];
[subscriber sendNext:@"15"];
[subscriber sendNext:@"wujy"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"執行清理");
}];
}];
[[signal ignoreValues] subscribeNext:^(id x) {
//它是沒機會執行 因爲ignoreValues已經忽略所有的next值
NSLog(@"ignoreValues當前值:%@",x);
} error:^(NSError *error) {
NSLog(@"ignoreValues error");
} completed:^{
NSLog(@"ignoreValues completed");
}];
distinctUntilChanged:當上一次的值和當前的值有明顯的變化就會發出信號,否則會被忽略掉。 // 過濾,當上一次和當前的值不一樣,就會發出內容。
// 在開發中,刷新UI經常使用,只有兩次數據不一樣才需要刷新
[[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"distinctUntilChanged%@",x);
}];
take:從開始一共取N次的信號
//1.創建信號
RACSubject *signal = [RACSubject subject];
//2.處理信號,訂閱信號
[[signal take:1] subscribeNext:^(id x) {
NSLog(@"take:%@",x);
}];
// 3.發送信號
[signal sendNext:@1];
[signal sendNext:@2];
takeLast:取最後N次的信號,前提條件,訂閱者必須調用完成,因爲只有完成,就知道總共有多少信號.
//1.創建信號
RACSubject *signal = [RACSubject subject];
//2.處理信號,訂閱信號
[[signal takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//3.發送信號
[signal sendNext:@1];
[signal sendNext:@332];
[signal sendNext:@333];
[signal sendCompleted];
takeUntil:(RACSignal *):獲取信號直到執行完這個信號 // 監聽文本框的改變,知道當前對象被銷燬
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
takeUntilBlock 對於每個next值,運行block,當block返回YES時停止取值
RACSignal *signal=[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"3"];
[subscriber sendNext:@"15"];
[subscriber sendNext:@"wujy"];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"執行清理");
}];
}];
[[signal takeUntilBlock:^BOOL(NSString *x) {
if ([x isEqualToString:@"15"]) {
return YES;
}
return NO;
}] subscribeNext:^(id x) {
NSLog(@"takeUntilBlock 獲取的值:%@",x);
}];
// 輸出
// takeUntilBlock 獲取的值:1
// takeUntilBlock 獲取的值:3
skip:(NSUInteger):跳過幾個信號,不接受。
[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"跳過幾個信號不接收%@",x);
}];
switchToLatest:用於signalOfSignals(信號的信號),有時候信號也會發出信號,會在signalOfSignals中,獲取signalOfSignals發送的最新信號。
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[signalOfSignals sendNext:signal];
// 獲取信號中信號最近發出信號,訂閱最近發出的信號。
// 注意switchToLatest:只能用於信號中的信號
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"獲取信號中信號最近發出信號,訂閱最近發出的信號%@",x);
}];
RAC操作方法三.
- doNext
- deliverOn
- timeout
- interval
- delay
- retry
- replay
- throttle
//ReactiveCocoa操作方法之秩序。
- (void)doNext {
//doNext: 執行Next之前,會先執行這個Block
//doCompleted: 執行sendCompleted之前,會先執行這個Block
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 執行[subscriber sendNext:@1];之前會調用這個Block
NSLog(@"doNext");
}] doCompleted:^{
// 執行[subscriber sendCompleted];之前會調用這個Block
NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
//ReactiveCocoa操作方法之線程。
- (void)deliverOn {
//deliverOn: 內容傳遞切換到制定線程中,副作用在原來線程中,把在創建信號時block中的代碼稱之爲副作用。
//subscribeOn: 內容傳遞和副作用都會切換到制定線程中。
}
//ReactiveCocoa操作方法之時間。
- (void)timeout {
//timeout:超時,可以讓一個信號在一定的時間後,自動報錯。
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
// 1秒後會自動調用
NSLog(@"%@",error);
}];
}
//interval 定時:每隔一段時間發出信號
- (void)interval {
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"%@",error);
}];
}
//delay 延遲發送next
- (void)delay {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
return nil;
}] delay:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
// ReactiveObjC操作方法之重複。
- (void)retry {
//retry重試 :只要失敗,就會重新執行創建信號中的block,直到成功.
__block int i = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 10) {
[subscriber sendNext:@1];
}else{
NSLog(@"接收到錯誤");
[subscriber sendError:nil];
}
i++;
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
}];
}
//replay重放:當一個信號被多次訂閱,反覆播放內容
- (void)replay {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"第一個訂閱者%@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"第二個訂閱者%@",x);
}];
}
throttle
- (void)throttle {
RACSubject *signal = [RACSubject subject];
_signal = signal;
// 節流,在一定時間(1秒)內,不接收任何信號內容,過了這個時間(1秒)獲取最後發送的信號內容發出。
[[signal throttle:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
demo地址:https://github.com/13662049573/TFY_ReactiveCocoaDemo.git