前言
學習 RACCommand 花了我很長一段時間,甚至一直搞不懂它的我一直想放棄學習 RAC,但是越是搞不懂,我就越想去征服它,于是就斷斷續(xù)續(xù)的看源碼,上 Google 搜文章,在這個周末的最后時間終于看到一絲出路,基本弄懂了 RACCommand 到底是干什么的以及它是如何做到這件事的。
按照我的經(jīng)驗,要弄懂 RACCommand,你必須先熟悉 RAC 中下面的內容:
- RACSignal
- RACSubject
- RACMulticastConnection
- RACSignal + Operation
- RACScheduler
- 以及對 RAC 基本的
值流概念的理解。
沒錯,你看 RACCommand 的源碼不過就二百多行,但是要去完全的理解它可需要下不少功夫,但是沒關系,很多時候如果你能先知道一個東西發(fā)明出來是為了解決什么問題,然后在一步步的去了解它解決這個問題的思路的話,那么理解它就會變得容易一些了。
理解 RACCommand
好,現(xiàn)在我就來說說 RACCommand 是用來干嘛的,我自己的理解就是:
RACCommand 是對一個動作的觸發(fā)以及它產(chǎn)生的后續(xù)事件的封裝。
看起來是比較抽象的,我來舉個栗子,就是登陸的流程,這個例子在很多介紹 RAC 的文章里都會講,但是他們通常是向你展示 RAC 有多方便而并沒有講 RACCommand,現(xiàn)在我通過這個例子來講 RACCommand。
看看最簡單的情況,有兩個 textfield 用于輸入用戶名和密碼,有一個 button 用于登陸。
我們把 button 的點擊稱為一個動作,但是通常來說有一個需求是,當用戶沒有輸入用戶名或密碼時,button 需要被禁止點擊。
也就是說這個動作是否能發(fā)生取決于一個條件,RACCommand 就封裝了這樣一個概念,相對應于它的一個屬性:
@property (nonatomic, strong, readonly) RACSignal *enabled;
它是一個信號,傳遞的應該是布爾值流,當值為 true 是,動作就可以執(zhí)行,反之。
接下來,當條件滿足之后,點擊 button 開始這個動作,產(chǎn)生一個事件,這時候第二個需求來了,如果多次點擊 button,只有當?shù)谝粋€事件完成之后,另一個事件才被允許產(chǎn)生。
也就是說多次觸發(fā)這個動作是否能讓相同的事件同時執(zhí)行,同樣,RACCommand 也封裝了這個概念:
@property (atomic, assign) BOOL allowsConcurrentExecution;
這個屬性是一個布爾值,用于控制動作是否允許同時執(zhí)行相同的事件。
然后事件就開始執(zhí)行了,第三個需求是,我想知道這個事件是否在執(zhí)行中,比如為了用于顯示一個 HUD。
也就是說需要監(jiān)控動作產(chǎn)生的事件執(zhí)行的狀態(tài),當然,RACCommand 也為我們提供了這個屬性:
@property (nonatomic, strong, readonly) RACSignal *executing;
這個屬性也是一個 signal,這個 signal 也是會產(chǎn)生一系列的布爾值,當事件在執(zhí)行過程中就會傳遞 true,結束了就傳遞 false。
很顯然,我們是需要知道事件執(zhí)行的結果的,RACCommand 為我們提供了另外一個屬性供我們使用:
@property (nonatomic, strong, readonly) RACSignal *executionSignals;
看這個變量名,你可能會有一絲疑惑,為什么是復數(shù),難道不是一個信號而是多個嗎?沒錯,你是想法是對的,executionSignals 其實是一個信號的信號,什么意思呢?就是這個信號傳遞的不是普通的值,而是一個個信號。想想為什么?因為之前說過一個動作可能會被多次觸發(fā),而同時產(chǎn)生多個事件開始執(zhí)行,這個信號傳遞的值就是用于傳遞事件執(zhí)行的結果值的信號,所以也可能有多個。
但是某些時候事件可能也會執(zhí)行失敗,我們需要捕獲那些錯誤的信息,怎么辦?RACCommand 也專門提供了這樣一個用于傳遞錯誤的信號:
@property (nonatomic, strong, readonly) RACSignal *errors;
這個信號就是用于傳遞事件失敗的信號的。
但這里,RACCommand 的基本概念就講完了。怎么樣?是不是感覺對 RACCommand 的理解清晰了不少?
開始使用 RACCommand
前面已經(jīng)提到了,要理解 RACCommand 的源碼需要了解的 RAC 知識比較多,因為我認為 RACCommand 本身就是對 RAC 的一次實踐,所以源碼理解起來還是還是比較困難的,雖然代碼不多??赐炅嘶A的概念,我們還是看看如何使用 RACCommand 吧。
一般來說,我們會把一個 command 和 UIControl 綁定,RAC 已經(jīng)給 UIControl 及其子類提供了這樣一個屬性 rac_command,我們只需要初始化一個 command 并且賦值給它就可以完成綁定了,就像下面這樣:
self.loginButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal empty]; // 這里返回一個需要的信號
}];
當然你還可以用指定一個 enable 信號的方式創(chuàng)建一個 command,這樣的話,control 的 enable 屬性也會和這個信號綁定。
這樣綁定完成后,每次點擊 button,command 就會開始執(zhí)行。
除了這種方式外,我們還常常會把網(wǎng)絡請求這種耗時的操作用 RACCommand 來實現(xiàn),就像之前理解的,通過它我們可以很容易的對網(wǎng)絡請求的狀態(tài)進行監(jiān)控,無論是參數(shù)、請求狀態(tài),還是 防止多次相同請求、錯誤處理 這樣的需求用 command 處理起來都比較方便。我們來看看實例:
首先,在 viewModel 中定義一個 command,
@property (nonatomic, strong) RACCommand *requestRealStuffCommand;
然后初始化它:
self.requestRealStuffCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString *dayString) {
return [[[[GKHttpClient sharedClient] getGankRealStuffForOneDay:dayString] map:^id(NSDictionary *json) {
NSArray *categories = json[@"category"];
NSDictionary *result = json[@"results"];
return [[categories.rac_sequence map:^id(NSString *cate) {
return result[cate];
}] foldLeftWithStart:@[] reduce:^id(NSArray *accumulator, id value) {
return [accumulator arrayByAddingObjectsFromArray:value];
}];
}] mtl_mapToArrayOfModelsWithClass:[RealStuff class]];
}];
我這里用封裝的 http client 來發(fā)起請求,并且直接將結果 map 為 model。
最后執(zhí)行 command 發(fā)起請求,注意我這里還用到了 command 的參數(shù)這個特性。
[self.requestRealStuffCommand execute:self.history[day]];
接著你可以監(jiān)聽請求的執(zhí)行狀態(tài):
[self.viewModel.requestRealStuffCommand.executing subscribeNext:^(NSNumber *x) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = x.boolValue;
}];
請求完成后刷新列表:
[[self.viewModel.requestRealStuffCommand.executionSignals switchToLatest] subscribeNext:^(id x) {
@strongify(self)
[self.tableView reloadData];
}];
更具體的使用細節(jié)可以參看我的這個 demo。