上一篇介紹了ReactiveCocoa
的常用類,已經基本滿足項目中的簡單應用要求,但是針對複雜的功能還需要其它的類來協同處理。ReactiveCocoa
提供了強大的流程處理功能來解決複雜的問題,包括事件點擊、代理、通知、事件同步和異步等等,可以簡化代碼體量,實現 高聚合、低耦合 的編程思想。下面ReactiveCocoa
的更強大的功能:
一. RACTuple、RACSequence
RACTuple: 元組類,類似NSArray,在解構對象中經常使用
RACSequence: 集合類,使用它來快速遍歷數組和字典
// 1.遍歷數組
NSArray *numbers = @[@1,@2,@3,@4];
// 這裏其實是三步
// 第一步: 把數組轉換成集合RACSequence numbers.rac_sequence
// 第二步: 把集合RACSequence轉換RACSignal信號類,numbers.rac_sequence.signal
// 第三步: 訂閱信號,激活信號,會自動把集合中的所有值,遍歷出來。
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組對象)
NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元組,會把元組的值,按順序給參數裏面的變量賦值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相當於以下寫法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"%@ %@",key,value);
}];
二. RACScheduler、RACUnit、RACEvent
RACScheduler: RAC中的隊列,用GCD封裝的
RACUnit: 表⽰stream不包含有意義的值,也就是看到這個,可以直接理解爲nil
RACEvent: 把數據包裝成信號事件(signal event)。它主要通過RACSignal的-materialize來使用,然並卵
三. 事件監聽
- 代替代理:
rac_signalForSelector
之前需要遵守代理協議、賦值delegate、實現代理方法等都不需要,只用rac_signalForSelector
就可以實現
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"點擊紅色按鈕");
}];
- 代替KVO:
rac_valuesAndChangesForKeyPath
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- 監聽事件:
rac_signalForControlEvents
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按鈕被點擊了");
}];
- 代替通知:
rac_addObserverForName
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"鍵盤彈出");
}];
- 監聽文本框文字改變:
rac_textSignal
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改變了%@",x);
}];
- 同步信號:
rac_liftSelector:withSignalsFromArray:Signals
- (void)viewDidLoad
{
RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 發送請求1
[subscriber sendNext:@"發送請求1"];
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id 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);
}
四. 常見宏
- RAC: 於給某個對象的某個屬性綁定
// 只要文本框文字改變,就會修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
- RACObserve: 監聽某個對象的某個屬性,返回的是信號
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
@weakify(Obj)
和@strongify(Obj)
:處理閉包強引用RACTuple
和RACTupleUnpack
: 元組的構造與解構
// 把參數中的數據包裝成元組
RACTuple *tuple = RACTuplePack(@10,@20);
// 把參數中的數據包裝成元組
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元組,會把元組的值,按順序給參數裏面的變量賦值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
五. 常用操作方法之映射
1. flattenMap
flattenMap
作用:把源信號的內容映射成一個新的信號,信號可以是任意類型
- 使用步驟
1.傳入一個block,block類型是返回值RACStream,參數value
2.參數value就是源信號的內容,拿到源信號的內容做處理
3.包裝成RACReturnSignal信號,返回出去。
- 底層實現
0.flattenMap內部調用bind方法實現的,flattenMap中block的返回值,會作爲bind中bindBlock的返回值。
1.當訂閱綁定信號,就會生成bindBlock。
2.當源信號發送內容,就會調用bindBlock(value, *stop)
3.調用bindBlock,內部就會調用flattenMap的block,flattenMap的block作用:就是把處理好的數據包裝成信號。
4.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
5.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送出來。
- 代碼實現
// 創建信號
RACSubject *subject = [RACSubject subject];
// 綁定信號
RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
// block:只要源信號發送內容就會調用
// value:就是源信號發送內容
value = [NSString stringWithFormat:@"%@",value];
NSLog(@"%@",value);
// block:只要源信號發送內容就會調用
// value:就是源信號發送內容
return [RACReturnSignal return:value];
}];
// flattenMap中返回的是什麼信號,訂閱的就是什麼信號
// 訂閱信號
[bindSignal subscribeNext:^(id x) {
NSLog(@"----%@",x);
}];
// 發送數據
[subject sendNext:@"abcd"];
2. Map
把源信號的值映射成一個新的值,返回一個對象
- 使用步驟
1.傳入一個block,類型是返回對象,參數是value
2.value就是源信號的內容,直接拿到源信號的內容做處理
3.把處理好的內容,直接返回就好了,不用包裝成信號,返回的值,就是映射的值。
- 底層實現
1.Map底層其實是調用flatternMap,Map中block中的返回的值會作爲flatternMap中block中的值。
2.當訂閱綁定信號,就會生成bindBlock。
3.當源信號發送內容,就會調用bindBlock(value, *stop)
4.調用bindBlock,內部就會調用flattenMap的block
5.flattenMap的block內部會調用Map中的block,把Map中的block返回的內容包裝成返回的信號。
6.返回的信號最終會作爲bindBlock中的返回信號,當做bindBlock的返回信號。
7.訂閱bindBlock的返回信號,就會拿到綁定信號的訂閱者,把處理完成的信號內容發送
- 代碼實現
RACSubject *subject = [RACSubject subject];
RACSignal *bindSignal = [subject map:^id(id value) {
// 返回的類型,就是你需要映射的值
return [NSString stringWithFormat:@"%@",value];
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"123"];
[subject sendNext:@"456"]
六. 操作方法之組合
1. 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中發送值,就會通過拼接信號的訂閱者把值發送出來
- 代碼實現
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 發送信號
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 發送信號
[subscriber sendNext:@"abc"];
return nil;
}];
// concat:按順序去連接
// 注意:concat,第一個信號必須要調用sendCompleted
// 創建組合信號
RACSignal *concatSignal = [signalA concat:signalB];
// 訂閱組合信號
[concatSignal subscribeNext:^(id x) {
// 既能拿到A信號的值,又能拿到B信號的值
NSLog(@"%@",x);
}];
2. then
用於連接兩個信號,當第一個信號完成,纔會連接then返回的信號。注意使用then,之前信號的值會被忽略掉
- 底層實現
1. 先過濾掉之前的信號發出的值。
2. 使用concat連接then返回的信號
- 代碼實現
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 發送信號
[subscriber sendNext:@"abc"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 發送信號
[subscriber sendNext:@"123"];
return nil;
}];
// 創建組合信號
// then:忽悠掉第一個信號所有值
RACSignal *thenSignal = [signalA then:^RACSignal *{
// 返回信號就是需要組合的信號
return signalB;
}];
// 訂閱信號
[thenSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
3. merge
把多個信號合併爲一個信號,任何一個信號有新值的時候就會調用
- 底層實現
1.合併信號被訂閱的時候,就會遍歷所有信號,並且發出這些信號。
2.每發出一個信號,這個信號就會被訂閱
3.也就是合併信號一被訂閱,就會訂閱裏面所有的信號。
4.只要有一個信號被髮出就會被監聽。
- 代碼實現
// 任意一個信號請求完成都會訂閱到
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 組合信號
RACSignal *mergeSignal = [signalA merge:signalB];
// 訂閱信號
[mergeSignal subscribeNext:^(id x) {
// 任意一個信號發送內容都會來這個block
NSLog(@"%@",x);
}];
// 發送數據
[signalA sendNext:@"abc"];
[signalB sendNext:@"123"];
4. zipWith
把兩個信號壓縮成一個信號,只有當兩個信號同時發出信號內容時,並且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的next事件
- 底層實現
1. 定義壓縮信號,內部就會自動訂閱signalA,signalB
2. 每當signalA或者signalB發出信號,就會判斷signalA,signalB有沒有發出個信號,有就會把最近發出的信號都包裝成元組發出。
- 代碼實現
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 壓縮成一個信號
// zipWith:當一個界面多個請求的時候,要等所有請求完成才能更新UI
// zipWith:等所有信號都發送內容的時候纔會調用
RACSignal *zipSignal = [signalA zipWith:signalB];
// 訂閱信號
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 發送信號
// 注意: 打印順序是由壓縮的信號順序決定的,不是由發送信號的順序決定的。
[signalB sendNext:@"123"];
[signalA sendNext:@"abc"];
5.reduce
用於信號發出的內容是元組,把信號發出元組的值聚合成一個值
- 底層實現
訂閱聚合信號,每次有內容發出,就會執行reduceblcok,把信號內容轉換成reduceblcok返回的值。
- 代碼實現
// 聚合
// 常見的用法,(先組合在聚合)。combineLatest:(id)signals reduce:(id (^)())reduceBlock
// reduce中的block簡介:
// reduceblcok中的參數,有多少信號組合,reduceblcok就有多少參數,每個參數就是之前信號發出的內容
// reduceblcok的返回值:聚合信號之後的內容。
RACSignal *reduceSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
// block:只要源信號發送內容就會調用,組合成新一個值
NSLog(@"%@ %@",account,pwd);
// 聚合的值就是組合信號的內容
return @(account.length && pwd.length);
}];
// 訂閱組合信號
// [reduceSiganl subscribeNext:^(id x) {
//
// _loginBtn.enabled = [x boolValue];
// }];
RAC(_loginBtn,enabled) = reduceSiganl;
6. combine
把兩個信號組合成一個信號,跟zip一樣,沒什麼區別
- 底層實現
1.當組合信號被訂閱,內部會自動訂閱signalA,signalB,必須兩個信號都發出內容,纔會被觸發。
2.並且把兩個信號組合成元組發出。
- 代碼實現
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@1];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@2];
return nil;
}];
// 把兩個信號組合成一個信號,跟zip一樣,沒什麼區別
RACSignal *combineSignal = [signalA combineLatestWith:signalB];
[combineSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
七、操作方法之過濾
1. filter
使用它可以獲取滿足條件的信號
- 代碼實現
// 只有當我們文本框的內容長度大於5,纔想要獲取文本框的內容
[[_textField.rac_textSignal filter:^BOOL(id value) {
// value: 源信號的內容
return [value length] > 5;
// 返回值,就是過濾條件,只有滿足這個條件,才能能獲取到內容
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
2.ignore
忽略某些值的信號
- 代碼實現
// ignore:忽略一些值
// ignoreValues:忽略所有的值
// 1.創建信號
RACSubject *subject = [RACSubject subject];
// 2.忽略一些
RACSignal *ignoreSignal = [subject ignoreValues];
// 3.訂閱信號
[ignoreSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 4.發送數據
[subject sendNext:@"2"];
[subject sendNext:@"456"];
[subject sendNext:@"789"];
3.distinctUntilChanged
當上一次的值和當前的值有明顯的變化就會發出信號,否則會被忽略掉
- 代碼實現
// distinctUntilChanged:如果當前的值跟上一個值相同,就不會被訂閱到
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@2];
[subject sendNext:@3];
4.take
從開始一共取N次的信號
- 代碼實現
// 1.創建信號
RACSubject *subject = [RACSubject subject];
// 2、處理信號,訂閱信號
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 3.發送信號
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
5. takeLast
取最後N次的信號,前提條件,訂閱者必須調用完成,因爲只有完成,就知道總共有多少信號
- 代碼實現
// 1.創建信號
RACSubject *signal = [RACSubject subject];
// 2、處理信號,訂閱信號
[[signal takeLast:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 3.發送信號
[signal sendNext:@1];
[signal sendNext:@2];
[signal sendNext:@3];
[signal sendCompleted];
6.takeUntil
獲取信號直到執行完這個信號,當發送信號爲空或結束時,後面有發送的信號,也不會執行
- 代碼實現
RACSubject *subject = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[[subject takeUntil:signal] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@"abc"];
[signal sendError:nil];
[signal sendNext:@2];
[signal sendNext:@3];
7.skip
跳過幾個信號,不接受
- 代碼實現
// skip;跳躍幾個值
RACSubject *subject = [RACSubject subject];
[[subject skip:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
八、操作方法之秩序
執行Next之前,會先執行這個Block
執行sendCompleted之前,會先執行這個Block
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}];
// 執行[subscriber sendNext:@1];之前會調用這個Block
[[[signal doNext:^(id x) {
NSLog(@"doNext");
}] doCompleted:^{
// 執行[subscriber sendCompleted];之前會調用這個Block
NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"---%@",x);
}];
九、操作方法之線程
deliverOn
內容傳遞切換到制定線程中,副作用在原來線程中,把在創建信號時block中的代碼稱之爲副作用
具體應用
_executing = [[[[[immediateExecuting
deliverOn:RACScheduler.mainThreadScheduler]
// This is useful before the first value arrives on the main thread.
startWith:@NO]
distinctUntilChanged]
replayLast]
setNameWithFormat:@"%@ -executing", self];
subscribeOn
內容傳遞和副作用都會切換到制定線程中
具體應用
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
十、操作方法之時間
1.timeout
超時,可以讓一個信號在一定的時間後,自動報錯
RACSignal *signalA = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@123];
});
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
[signalA subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
// 1秒後會自動調用
NSLog(@"%@",error);
}];
2.interval
定時:每隔一段時間發出信號
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
return nil;
//delay 延遲發送next。
}] delay:2.0];
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
3. delay
延遲發送next
[[RACSignal interval:1.0 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"123");
}];
十一、操作方法之重複
1. retry
只要失敗,就會重新執行創建信號中的block,直到成功.
__block int i = 0;
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
if (i == 5) {
[subscriber sendNext:@123];
} else{
NSLog(@"error");
[subscriber sendError:nil];
}
i++;
return nil;
}];
[[signal retry] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"%@",error);
}];
2. replay
當一個信號被多次訂閱,反覆播放內容
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
[subscriber sendNext:@456];
return nil;
}];
[[signal replay] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[[signal replay] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
3. throttle
當某個信號發送比較頻繁時,可以使用節流,在某一段時間不發送信號內容,過了一段時間獲取信號的最新內容發出
RACSubject *signal = [RACSubject subject];
_signal = signal;
// 節流,在一定時間(1秒)內,不接收任何信號內容,過了這個時間(1秒)獲取最後發送的信號內容發出。
// [[signal throttle:2.0] subscribeNext:^(id x) {
// NSLog(@"%@",x);
// }];
[[signal throttle:1.0 valuesPassingTest:^BOOL(id next) {
return YES;
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
int i = 0;
[_signal sendNext:@(i)];
i++;
}
未完待續…