ReactiveCocoa 學習筆記(三)之 RACCommand

前言

學習 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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容