學習 RAC 我們首先要了解 RAC 都有哪些類
- RACSignal
- RACSubject
- RACSequence
- RACMulticastConnection
- RACCommand
在學習的時候寫了一個小 demo 來分別介紹每個類的作用,gitHub 地址: https://github.com/SummerHH/ReactiveCocoa.git
demo 的目錄結構如下
RAC學習起來的特點
- 學習起來比較難
- 團隊開發的時候需要謹慎使用
- 團隊代碼需要不斷的評審,保證團隊中所有人代碼的風格一致!避免閱讀代碼的困難
RAC 導入:
使用 pod 'ReactiveCocoa', '~> 2.5' 導入
這里版本說下2.5以下是 Object-C 不支持 swift
2.5 以上開始支持 swift
項目如果是 OC 寫的話建議導入2.5這個版本
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); }];