前言
眾所周知RAC
學習曲線陡峭,作為新手的我也踩了不少坑.學習Rx的過程首先要學會基本的操作,然后再組合操作完成復雜的需求,就像習武先學基本的招式,再利用基本招式產生組合拳.網上關于RAC
組合拳
的優質文章并不多,在網上所搜到的項目大部分對Rx
的運用也非常粗糙單調,這里我介紹此人的一系列文章.這篇博客的目的是給自己的學習之路做做筆記,記錄一下踩的坑和得到的收獲,希望也能幫到有需要的人.
需求
在開發中,很多時候我們需要在N個網絡請求完畢之后再進行UI界面的更新.假設現在我們有A請求和B請求,并且這兩個請求是并行請求,沒有順序執行關系,在A,B請求完成之后刷新界面.
假設現在開始兩個網絡請求:
[self.viewModel.commandA execute:nil];
[self.viewModel.commandB execute:nil];
然后需要在網絡請求命令執行的時候轉菊花或者進行下拉刷新的UI動作.
由于有多個網絡請求,所以可以將多個請求的執行信號合并成一個信號.
@weakify(self);
[[RACSignal
merge:@[
self.viewModel.commandA.executing,
self.viewModel.commandB.executing
]]
subscribeNext:^(NSNumber *loading) {
@strongify(self);
NSLog(@"%@",loading);
if (loading.boolValue) {
[self.tableView.header beginRefreshing];
} else {
[self.tableView.header endRefreshing];
[self.tableView reloadData];
}
}];
得到的log
值為:
0
0
1
1
0
0
但是這種做法有問題,我們先看看merge
操作的時序圖:
可以看到,merge
操作所產生的新信號能夠在被訂閱時得到原信號們所產生的輸出值.也就是說我們subscribeNext
訂閱到的值是兩個executing
信號實時的輸出的集合.但是當其中一個網絡操作較另一個操作耗時時,其中一個執行信號輸出0
表示執行完畢,并對界面進行了刷新,然而另一個操作N秒之后才會執行完成.如log
所示,當第一個請求完成輸出0
時,第二個請求還在進行.
這樣的情況顯然不是我們想要的結果.
因此,merge
方法并不適合該場景,應該換成combineLatest
.
combineLatest
能在每個被訂閱信號發生變化的時候,都會取得每個信號的最新值應用在函數上.在我們的例子中,每個executing
信號發生變化,我們還要對其他信號的狀態進行觀察,才能判斷現在所處的是否是刷新狀態.我們將方法替換掉:
@weakify(self);
[[RACSignal
combineLatest:@[
self.viewModel.commandA.executing,
self.viewModel.commandB.executing
]]
subscribeNext:^(NSNumber *loading) {
@strongify(self);
NSLog(@"%@",loading);
if (loading.boolValue) {
[self.tableView.header beginRefreshing];
} else {
[self.tableView.header endRefreshing];
[self.tableView reloadData];
}
}];
此時的代碼運行會出錯,因為訂閱得到的值loading
實際上是一個RACTuple
,里面包含了每個被訂閱信號上最新的值.
其實我們只關心當前是不是處于網絡請求狀態,也就是說,我們只關心兩種情況.
第一種情況,RACTuple
中的值都為0,說明網絡請求還沒開始,或者網絡請求已經結束.
第二種情況,RACTuple
中有一個值為1,說明正在網絡請求中.
我們拿到這個RACTuple
,可以做map
或reduce
操作,但還有更高效的做法.
實際上,這個操作就是邏輯或
操作,真值表為:
A | B | OR |
---|---|---|
1 | 1 | 1 |
0 | 1 | 1 |
1 | 0 | 1 |
0 | 0 | 0 |
RAC中提供了or
操作的方法.
@weakify(self);
[[[RACSignal
combineLatest:@[
self.viewModel.commandA.executing,
self.viewModel.commandB.executing
]]
or]
subscribeNext:^(NSNumber *loading) {
@strongify(self);
NSLog(@"%@",loading);
if (loading.boolValue) {
[self.tableView.header beginRefreshing];
} else {
[self.tableView.header endRefreshing];
[self.tableView reloadData];
}
}];
此時log
如下:
0
1
1
1
0
和最開始的log
相比,為什么少了個剛開始的0
呢?仔細看上方combineLatest
的圖,當兩個信號都發送值時才有第一個值合并,這點又和merge
有區別.
我們似乎完成了我們的目的,但代碼還可以繼續優化.比如剛開始得到的0
,這應該是信號初始發出的,對于我們來說根本沒有用,我們可以排除這個值的干擾.
@weakify(self);
[[[[RACSignal
combineLatest:@[
self.viewModel.commandA.executing,
self.viewModel.commandB.executing
]]
or]
skipWhileBlock:^BOOL(NSNumber *loading) {
return ![loading boolValue];
}]
subscribeNext:^(NSNumber *loading) {
@strongify(self);
NSLog(@"%@",loading);
if (loading.boolValue) {
[self.tableView.header beginRefreshing];
} else {
[self.tableView.header endRefreshing];
[self.tableView reloadData];
}
}];
我們增加了skipWhileBlock
方法.當得到第一個返回值為yes
之前,后面的subscribeNext
將不會訂閱到值.
在第一個值為
1
之前,所有的值都丟棄.得到log
:
1
1
1
0
很好,我們又進了一步.接下來還有點問題,連續三次的1
也可以進行過濾,因為正在進行刷新狀態,不用每次得到1
又進行刷新.
@weakify(self);
[[[[[RACSignal
combineLatest:@[
self.viewModel.commandA.executing,
self.viewModel.commandB.executing
]]
or]
skipWhileBlock:^BOOL(NSNumber *loading) {
return ![loading boolValue];
}]
distinctUntilChanged]
subscribeNext:^(NSNumber *loading) {
@strongify(self);
NSLog(@"%@",loading);
if (loading.boolValue) {
[self.tableView.header beginRefreshing];
} else {
[self.tableView.header endRefreshing];
[self.tableView reloadData];
}
}];
distinctUntilChanged
這個方法很好理解.
此時的log
:
1
0
完美!