轉載自最快讓你上手ReactiveCocoa之進階篇,稍做修改javascript
全部的信號(RACSignal)均可以進行操做處理,由於全部操做方法都定義在RACStream.h中,所以只要繼承RACStream就有了操做處理方法。css
運用的是Hook(鉤子)思想,Hook是一種用於改變API(應用程序編程接口:方法)執行結果的技術.html
Hook用處:截獲API調用的技術。java
有關Hook的知識能夠看個人這篇博客《Objective-C Runtime 的一些基本使用》中的 更換代碼的實現方法 一節,ios
Hook原理:在每次調用一個API返回結果以前,先執行你本身的方法,改變結果的輸出。git
ReactiveCocoa 操做的核心方法是 bind(綁定),並且也是RAC中核心開發方式。以前的開發方式是賦值,而用RAC開發,應該把重心放在綁定,也就是能夠在建立一個對象的時候,就綁定好之後想要作的事情,而不是等賦值以後在去作事情。github
列如,把數據展現到控件上,以前都是重寫控件的 setModel
方法,用RAC就能夠在一開始建立控件的時候,就綁定好數據。編程
做用api
RAC底層都是調用bind, 在開發中不多直接使用 bind 方法,bind屬於RAC中的底層方法,咱們只須要調用封裝好的方法,bind用做了解便可.數組
bind方法使用步驟
RACStreamBindBlock
的 block。RACStreamBindBlock
類型的 bindBlock
做爲block的返回值。bindBlock
的返回值。注意:在bindBlock中作信號結果的處理。
bind方法參數
RACStreamBindBlock:
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);
參數一(value)
:表示接收到信號的原始值,還沒作處理
參數二(*stop)
:用來控制綁定Block,若是*stop = yes,那麼就會結束綁定。
返回值
:信號,作好處理,在經過這個信號返回出去,通常使用 RACReturnSignal
,須要手動導入頭文件RACReturnSignal.h
使用
假設想監聽文本框的內容,而且在每次輸出結果的時候,都在文本框的內容拼接一段文字「輸出:」
使用封裝好的方法:在返回結果後,拼接。
[_textField.rac_textSignal subscribeNext:^(id x) { // 在返回結果後,拼接 輸出: NSLog(@"輸出:%@",x); }];
使用RAC中 bind
方法作處理,在返回結果前,拼接。
這裏須要手動導入#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); }]; ```
底層實現
didSubscribe
,生成一個 bindingBlock
。bindingBlock
處理,調用bindingBlock(value,stop)
bindingBlock(value,stop)
,會返回一個內容處理完成的信號RACReturnSignal
。RACReturnSignal
,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。注意:不一樣訂閱者,保存不一樣的nextBlock,看源碼的時候,必定要看清楚訂閱者是哪一個。
映射主要用這兩個方法實現:flattenMap,Map,用於把源信號內容映射成新的內容。
做用
把源信號的內容映射成一個新的信號,信號能夠是任意類型
使用步驟
RACStream
,參數valueRACReturnSignal
信號,返回出去。使用
監聽文本框的內容改變,把結構從新映射成一個新值.
[[_textField.rac_textSignal flattenMap:^RACStream *(id value) { // block調用時機:信號源發出的時候 // block做用:改變信號的內容 // 返回RACReturnSignal return [RACReturnSignal return:[NSString stringWithFormat:@"信號內容:%@", value]]; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
底層實現
bind
方法實現的,flattenMap中block的返回值,會做爲bind中bindBlock的返回值。bindBlock
。bindBlock(value, *stop)
bindBlock
,內部就會調用 flattenMap 的 bloc k,flattenMap 的block做用:就是把處理好的數據包裝成信號。bindBlock
中的返回信號,當作 bindBlock
的返回信號。bindBlock
的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。做用
把源信號的值映射成一個新的值
使用步驟
value
value
就是源信號的內容,直接拿到源信號的內容作處理使用
監聽文本框的內容改變,把結構從新映射成一個新值.
[[_textField.rac_textSignal map:^id(id value) { // 拼接完後,返回對象 return [NSString stringWithFormat:@"信號內容: %@", value]; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
底層實現:
flatternMa
p,Map
中block中的返回的值會做爲 flatternMap
中block中的值bindBlock
bindBlock(value, *stop)
bindBlock
,內部就會調用 flattenMap的block
flattenMap的block
內部會調用 Map
中的block,把 Map
中的block返回的內容包裝成返回的信號bindBlock
中的返回信號,當作 bindBlock
的返回信號bindBlock
的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。Map
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(@"signalOfsignals:%@",x); }]; // 信號的信號發送信號 [signalOfsignals sendNext:signal]; // 信號發送內容 [signal sendNext:@"hi"];
組合就是將多個信號按照某種規則進行拼接,合成新的信號。
做用
按順序拼接信號,當多個信號發出的時候,有順序的接收信號。
底層實現
使用步驟
concat:
拼接信號使用
拼接信號 signalA
、 signalB
、 signalC
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"Hello"]; [subscriber sendCompleted]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"World"]; [subscriber sendCompleted]; return nil; }]; RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"!"]; [subscriber sendCompleted]; return nil; }]; // 拼接 A B, 把signalA拼接到signalB後,signalA發送完成,signalB纔會被激活。 RACSignal *concatSignalAB = [signalA concat:signalB]; // A B + C RACSignal *concatSignalABC = [concatSignalAB concat:signalC]; // 訂閱拼接的信號, 內部會按順序訂閱 A->B->C // 注意:第一個信號必須發送完成,第二個信號纔會被激活... [concatSignalABC subscribeNext:^(id x) { NSLog(@"%@", x); }];
做用
用於鏈接兩個信號,當第一個信號完成,纔會鏈接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); }]; /// 輸出:2
注意
注意使用then
,以前信號的值會被忽略掉.
做用
合併信號,任何一個信號發送數據,都能監聽到.
底層實現
使用
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B"]; return nil; }]; // 合併信號, 任何一個信號發送數據,都能監聽到 RACSignal *mergeSianl = [signalA merge:signalB]; [mergeSianl subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 13:29:08.013 ReactiveCocoa進階[3627:718315] A 2017-01-03 13:29:08.014 ReactiveCocoa進階[3627:718315] B
##### zip - **做用** 把兩個信號壓縮成一個信號,只有當兩個信號 **同時** 發出信號內容時,而且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的next事件。 - **底層實現** 1. 定義壓縮信號,內部就會自動訂閱signalA,signalB 2. 每當signalA或者signalB發出信號,就會判斷signalA,signalB有沒有發出個信號,有就會把每一個信號 第一次 發出的值包裝成元組發出 - **使用** ``` RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *zipSignal = [signalA zipWith:signalB]; [zipSignal subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 13:48:09.234 ReactiveCocoa進階[3997:789720] zipWith: <RACTuple: 0x600000004df0> ( A1, B1 ) 2017-01-03 13:48:09.234 ReactiveCocoa進階[3997:789720] zipWith: <RACTuple: 0x608000003410> ( A2, B2 ) ``` ##### combineLatest - **做用** 將多個信號合併起來,而且拿到各個信號最後一個值,必須每一個合併的signal至少都有過一次sendNext,纔會觸發合併的信號。 - **底層實現** 1. 當組合信號被訂閱,內部會自動訂閱signalA,signalB,必須兩個信號都發出內容,纔會被觸發。 2. 而且把兩個信號的 最後一次 發送的值組合成元組發出。 - **使用** ``` RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *combineSianal = [signalA combineLatestWith:signalB]; [combineSianal subscribeNext:^(id x) { NSLog(@"combineLatest:%@", x); }]; // 輸出 2017-01-03 13:48:09.235 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x60800000e150> ( A2, B1 ) 2017-01-03 13:48:09.235 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x600000004db0> ( A2, B2 ) 2017-01-03 13:48:09.236 ReactiveCocoa進階[3997:789720] combineLatest:<RACTuple: 0x60800000e180> ( A2, B3 ) ``` - **注意** **combineLatest**與**zip**用法類似,必須每一個合併的signal至少都有過一次sendNext,纔會觸發合併的信號。 區別看下圖: ![](https://ww2.sinaimg.cn/large/006y8lVagw1fbdf6cyez6j30id0kkabf.jpg) ##### reduce - **做用** 把信號發出元組的值聚合成一個值 - **底層實現** 1. 訂閱聚合信號, 2. 每次有內容發出,就會執行reduceblcok,把信號內容轉換成reduceblcok返回的值。 - **使用** 常見的用法,(先組合在聚合)`combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock` reduce中的block簡介: reduceblcok中的參數,有多少信號組合,reduceblcok就有多少參數,每一個參數就是以前信號發出的內容 reduceblcok的返回值:聚合信號以後的內容。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A1"]; [subscriber sendNext:@"A2"]; return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B1"]; [subscriber sendNext:@"B2"]; [subscriber sendNext:@"B3"]; return nil; }]; RACSignal *reduceSignal = [RACSignal combineLatest:@[signalA, signalB] reduce:^id(NSString *str1, NSString *str2){ return [NSString stringWithFormat:@"%@ %@", str1, str2]; }]; [reduceSignal subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 輸出 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B1 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B2 2017-01-03 15:42:41.803 ReactiveCocoa進階[4248:1264674] A2 B3 ```
過濾就是過濾信號中的 特定值 ,或者過濾指定 發送次數 的信號。
做用
過濾信號,使用它能夠獲取知足條件的信號.
block的返回值是Bool值,返回NO
則過濾該信號
使用
// 過濾: // 每次信號發出,會先執行過濾條件判斷. [[_textField.rac_textSignal filter:^BOOL(NSString *value) { NSLog(@"原信號: %@", value); // 過濾 長度 <= 3 的信號 return value.length > 3; }] subscribeNext:^(id x) { NSLog(@"長度大於3的信號:%@", x); }]; // 在_textField中輸出12345 // 輸出 2017-01-03 16:36:54.938 ReactiveCocoa進階[4714:1552910] 原信號: 1 2017-01-03 16:36:55.383 ReactiveCocoa進階[4714:1552910] 原信號: 12 2017-01-03 16:36:55.706 ReactiveCocoa進階[4714:1552910] 原信號: 123 2017-01-03 16:36:56.842 ReactiveCocoa進階[4714:1552910] 原信號: 1234 2017-01-03 16:36:56.842 ReactiveCocoa進階[4714:1552910] 長度大於3的信號:1234 2017-01-03 16:36:58.350 ReactiveCocoa進階[4714:1552910] 原信號: 12345 2017-01-03 16:36:58.351 ReactiveCocoa進階[4714:1552910] 長度大於3的信號:12345
做用
忽略某些信號.
使用
做用
忽略某些值的信號.
底層調用了 filter
與 過濾值進行比較,若相等返回則 NO
使用
// 內部調用filter過濾,忽略掉字符爲 @「1」的值
[[_textField.rac_textSignal ignore:@"1"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
##### distinctUntilChanged - **做用** 當上一次的值和當前的值有明顯的變化就會發出信號,不然會被忽略掉。 - **使用** ``` [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) { NSLog(@"%@",x); }]; ``` ##### skip - **做用** 跳過 **第N次** 的發送的信號. - **使用** ``` // 表示輸入第一次,不會被監聽到,跳過第一次發出的信號 [[_textField.rac_textSignal skip:1] subscribeNext:^(id x) { NSLog(@"%@",x); }]; ``` ##### take - **做用** 取 **前N次** 的發送的信號. - **使用** ``` RACSubject *subject = [RACSubject subject] ; // 取 前兩次 發送的信號 [[subject take:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; [subject sendNext:@1]; [subject sendNext:@2]; [subject sendNext:@3]; // 輸出 2017-01-03 17:35:54.566 ReactiveCocoa進階[4969:1677908] 1 2017-01-03 17:35:54.567 ReactiveCocoa進階[4969:1677908] 2 ``` ##### takeLast - **做用** 取 **最後N次** 的發送的信號 前提條件,訂閱者必須調用完成 `sendCompleted`,由於只有完成,就知道總共有多少信號. - **使用** ``` RACSubject *subject = [RACSubject subject] ; // 取 後兩次 發送的信號 [[subject takeLast:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; [subject sendNext:@1]; [subject sendNext:@2]; [subject sendNext:@3]; // 必須 跳用完成 [subject sendCompleted]; ``` ##### takeUntil - **做用** 獲取信號直到某個信號執行完成 - **使用** ``` // 監聽文本框的改變直到當前對象被銷燬 [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal]; ``` ##### switchToLatest - **做用** 用於signalOfSignals(信號的信號),有時候信號也會發出信號,會在`signalOfSignals`中,獲取`signalOfSignals`發送的最新信號。 - **注意** switchToLatest:只能用於信號中的信號 - **使用** ``` RACSubject *signalOfSignals = [RACSubject subject]; RACSubject *signal = [RACSubject subject]; // 獲取信號中信號最近發出信號,訂閱最近發出的信號。 [signalOfSignals.switchToLatest subscribeNext:^(id x) { NSLog(@"%@", x); }]; [signalOfSignals sendNext:signal]; [signal sendNext:@1]; ``` #### 秩序 秩序包括 `doNext` 和 `doCompleted` 這兩個方法,主要是在 執行`sendNext` 或者 `sendCompleted`以前,先執行這些方法中Block。 ##### doNext 執行`sendNext`以前,會先執行這個`doNext`的 Block ##### doCompleted 執行`sendCompleted`以前,會先執行這`doCompleted`的`Block`
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hi"]; [subscriber sendCompleted]; return nil;
}] doNext:^(id x) {
// 執行 [subscriber sendNext:@"hi"] 以前會調用這個 Block NSLog(@"doNext");
}] doCompleted:^{
// 執行 [subscriber sendCompleted] 以前會調用這 Block NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
#### 線程 **ReactiveCocoa** 中的線程操做 包括 `deliverOn` 和 `subscribeOn`這兩種,將 *傳遞的內容* 或 建立信號時 *block中的代碼* 切換到指定的線程中執行。 ##### deliverOn - **做用** 內容傳遞切換到指定線程中,反作用在原來線程中,把在建立信號時block中的代碼稱之爲反作用。 - **使用** ``` // 在子線程中執行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"%@", [NSThread currentThread]); [subscriber sendNext:@123]; [subscriber sendCompleted]; return nil; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) { NSLog(@"%@", x); NSLog(@"%@", [NSThread currentThread]); }]; }); // 輸出 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224535] <NSThread: 0x608000270f00>{number = 3, name = (null)} 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224482] 123 2017-01-04 10:35:55.415 ReactiveCocoa進階[1183:224482] <NSThread: 0x600000079bc0>{number = 1, name = main} ``` 能夠看到`反作用`在 *子線程* 中執行,而 `傳遞的內容` 在 *主線程* 中接收 ##### subscribeOn - **做用** **subscribeOn**則是將 `內容傳遞` 和 `反作用` 都會切換到指定線程中 - **使用** ``` dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSLog(@"%@", [NSThread currentThread]); [subscriber sendNext:@123]; [subscriber sendCompleted]; return nil; }] subscribeOn:[RACScheduler mainThreadScheduler]] //傳遞的內容到主線程中 subscribeNext:^(id x) { NSLog(@"%@", x); NSLog(@"%@", [NSThread currentThread]); }]; }); // 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] <NSThread: 0x608000077640>{number = 1, name = main} 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] 123 2017-01-04 10:44:47.558 ReactiveCocoa進階[1243:275126] <NSThread: 0x608000077640>{number = 1, name = main} ``` `內容傳遞` 和 `反作用` 都切換到了 *主線程* 執行 #### 時間 時間操做就會設置信號超時,定時和延時。 ##### interval 定時 - **做用** 定時:每隔一段時間發出信號 ``` // 每隔1秒發送信號,指定當前線程執行 [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) { NSLog(@"定時:%@", x); }]; // 輸出 2017-01-04 13:48:55.196 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:55 +0000 2017-01-04 13:48:56.195 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:56 +0000 2017-01-04 13:48:57.196 ReactiveCocoa進階[1980:492724] 定時:2017-01-04 05:48:57 +0000 ``` ##### timeout 超時 - **做用** 超時,可讓一個信號在必定的時間後,自動報錯。 ``` RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { // 不發送信號,模擬超時狀態 // [subscriber sendNext:@"hello"]; //[subscriber sendCompleted]; return nil; }] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 設置1秒超時 [signal subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"%@", error); }]; // 執行代碼 1秒後 輸出: 2017-01-04 13:48:55.195 ReactiveCocoa進階[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)" ``` ##### delay 延時 - **做用** 延時,延遲一段時間後發送信號 ``` RACSignal *signal2 = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"延遲輸出"]; return nil; }] delay:2] subscribeNext:^(id x) { NSLog(@"%@", x); }]; // 執行代碼 2秒後 輸出 2017-01-04 13:55:23.751 ReactiveCocoa進階[2030:525038] 延遲輸出 ``` #### 重複 ##### retry - **做用** 重試:只要 發送錯誤 `sendError:`,就會 從新執行 建立信號的Block 直到成功 ``` __block int i = 0; [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { if (i == 5) { [subscriber sendNext:@"Hello"]; } else { // 發送錯誤 NSLog(@"收到錯誤:%d", i); [subscriber sendError:nil]; } i++; return nil; }] retry] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"%@", error); }]; // 輸出 2017-01-04 14:36:51.594 ReactiveCocoa進階[2443:667226] 收到錯誤信息:0 2017-01-04 14:36:51.595 ReactiveCocoa進階[2443:667226] 收到錯誤信息:1 2017-01-04 14:36:51.595 ReactiveCocoa進階[2443:667226] 收到錯誤信息:2 2017-01