ReactiveCocoa 中 RACCommand底層實現(xiàn)分析

前言

在使用ReactiveCocoa 過程中祥绞,除去RACSignal和RACSubject這些信號類以外冬阳,有些時候我們可能還需要封裝一些固定的操作集合疆柔。這些操作集合都是固定的,每次只要一觸發(fā)就會執(zhí)行事先定義好的一個過程镶柱。在iOS開發(fā)過程中旷档,按鈕的點(diǎn)擊事件就可能有這種需求。那么RACCommand就可以實現(xiàn)這種需求歇拆。

當(dāng)然除了封裝一個操作集合以外鞋屈,RACCommand還能集中處理錯誤等等功能厂庇。今天就來從底層來看看RACCommand是如何實現(xiàn)的。

目錄

  • 1.RACCommand的定義
  • 2.initWithEnabled: signalBlock: 底層實現(xiàn)分析
  • 3.execute:底層實現(xiàn)分析
  • 4.RACCommand的一些Category

一. RACCommand的定義

首先說說RACCommand的作用输吏。
RACCommand 在ReactiveCocoa 中是對一個動作的觸發(fā)條件以及它產(chǎn)生的觸發(fā)事件的封裝拄氯。

  • 觸發(fā)條件:初始化RACCommand的入?yún)nabledSignal就決定了RACCommand是否能開始執(zhí)行。入?yún)nabledSignal就是觸發(fā)條件它浅。舉個例子译柏,一個按鈕是否能點(diǎn)擊,是否能觸發(fā)點(diǎn)擊事情姐霍,就由入?yún)nabledSignal決定鄙麦。

  • 觸發(fā)事件:初始化RACCommand的另外一個入?yún)?RACSignal * (^)(id input))signalBlock就是對觸發(fā)事件的封裝典唇。RACCommand每次執(zhí)行都會調(diào)用一次signalBlock閉包。

RACCommand最常見的例子就是在注冊登錄的時候胯府,點(diǎn)擊獲取驗證碼的按鈕介衔,這個按鈕的點(diǎn)擊事件和觸發(fā)條件就可以用RACCommand來封裝,觸發(fā)條件是一個信號盟劫,它可以是驗證手機(jī)號夜牡,驗證郵箱,驗證身份證等一些驗證條件產(chǎn)生的enabledSignal侣签。觸發(fā)事件就是按鈕點(diǎn)擊之后執(zhí)行的事件塘装,可以是發(fā)送驗證碼的網(wǎng)絡(luò)請求。

RACCommand在ReactiveCocoa中算是很特別的一種存在影所,因為它的實現(xiàn)并不是FRP實現(xiàn)的蹦肴,是OOP實現(xiàn)的。RACCommand的本質(zhì)就是一個對象猴娩,在這個對象里面封裝了4個信號阴幌。

關(guān)于RACCommand的定義如下:


@interface RACCommand : NSObject
@property (nonatomic, strong, readonly) RACSignal *executionSignals;
@property (nonatomic, strong, readonly) RACSignal *executing;
@property (nonatomic, strong, readonly) RACSignal *enabled;
@property (nonatomic, strong, readonly) RACSignal *errors;
@property (atomic, assign) BOOL allowsConcurrentExecution;
volatile uint32_t _allowsConcurrentExecution;

@property (atomic, copy, readonly) NSArray *activeExecutionSignals;
NSMutableArray *_activeExecutionSignals;

@property (nonatomic, strong, readonly) RACSignal *immediateEnabled;
@property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input);
@end

RACCommand中4個最重要的信號就是定義開頭的那4個信號,executionSignals卷中,executing矛双,enabled,errors蟆豫。需要注意的是议忽,這4個信號基本都是(并不是完全是)在主線程上執(zhí)行的

1. RACSignal *executionSignals

executionSignals是一個高階信號十减,所以在使用的時候需要進(jìn)行降階操作栈幸,降價操作在前面分析過了,在ReactiveCocoa v2.5中只支持3種降階方式帮辟,flatten速址,switchToLatest,concat由驹。降階的方式就根據(jù)需求來選取芍锚。

還有選擇原則是,如果在不允許Concurrent并發(fā)的RACCommand中一般使用switchToLatest蔓榄。如果在允許Concurrent并發(fā)的RACCommand中一般使用flatten闹炉。

2. RACSignal *executing

executing這個信號就表示了當(dāng)前RACCommand是否在執(zhí)行,信號里面的值都是BOOL類型的润樱。YES表示的是RACCommand正在執(zhí)行過程中,命名也說明的是正在進(jìn)行時ing羡棵。NO表示的是RACCommand沒有被執(zhí)行或者已經(jīng)執(zhí)行結(jié)束壹若。

3. RACSignal *enabled

enabled信號就是一個開關(guān),RACCommand是否可用。這個信號除去以下2種情況會返回NO:

  • RACCommand 初始化傳入的enabledSignal信號店展,如果返回NO养篓,那么enabled信號就返回NO。
  • RACCommand開始執(zhí)行中赂蕴,allowsConcurrentExecution為NO柳弄,那么enabled信號就返回NO。

除去以上2種情況以外概说,enabled信號基本都是返回YES碧注。

4. RACSignal *errors

errors信號就是RACCommand執(zhí)行過程中產(chǎn)生的錯誤信號。這里特別需要注意的是:在對RACCommand進(jìn)行錯誤處理的時候糖赔,我們不應(yīng)該使用subscribeError:對RACCommand的executionSignals
進(jìn)行錯誤的訂閱
萍丐,因為executionSignals這個信號是不會發(fā)送error事件的,那當(dāng)RACCommand包裹的信號發(fā)送error事件時放典,我們要怎樣去訂閱到它呢逝变?應(yīng)該用subscribeNext:去訂閱錯誤信號


[commandSignal.errors subscribeNext:^(NSError *x) {     
    NSLog(@"ERROR! --> %@",x);
}];

5. BOOL allowsConcurrentExecution

allowsConcurrentExecution是一個BOOL變量奋构,它是用來表示當(dāng)前RACCommand是否允許并發(fā)執(zhí)行壳影。默認(rèn)值是NO。

如果allowsConcurrentExecution為NO弥臼,那么RACCommand在執(zhí)行過程中宴咧,enabled信號就一定都返回NO,不允許并發(fā)執(zhí)行醋火。如果allowsConcurrentExecution為YES悠汽,允許并發(fā)執(zhí)行。

如果是允許并發(fā)執(zhí)行的話芥驳,就會出現(xiàn)多個信號就會出現(xiàn)一起發(fā)送值的情況柿冲。那么這種情況產(chǎn)生的高階信號一般可以采取flatten(等效于flatten:0,+merge:)的方式進(jìn)行降階兆旬。

這個變量在具體實現(xiàn)中是用的volatile原子的操作假抄,在實現(xiàn)中重寫了它的get和set方法。


// 重寫 get方法
- (BOOL)allowsConcurrentExecution {
    return _allowsConcurrentExecution != 0;
}

// 重寫 set方法
- (void)setAllowsConcurrentExecution:(BOOL)allowed {
    [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)];
    
    if (allowed) {
        OSAtomicOr32Barrier(1, &_allowsConcurrentExecution);
    } else {
        OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution);
    }
    
    [self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)];
}


OSAtomicOr32Barrier是原子運(yùn)算丽猬,它的意義是進(jìn)行邏輯的“或”運(yùn)算宿饱。通過原子性操作訪問被volatile修飾的_allowsConcurrentExecution對象即可保障函數(shù)只執(zhí)行一次。相應(yīng)的OSAtomicAnd32Barrier也是原子運(yùn)算脚祟,它的意義是進(jìn)行邏輯的“與”運(yùn)算谬以。

6. NSArray *activeExecutionSignals

這個NSArray數(shù)組里面裝了一個個有序排列的,執(zhí)行中的信號由桌。NSArray的數(shù)組是可以被KVO監(jiān)聽的为黎。


- (NSArray *)activeExecutionSignals {
    @synchronized (self) {
        return [_activeExecutionSignals copy];
    }
}

當(dāng)然內(nèi)部還有一個NSMutableArray的版本邮丰,NSArray數(shù)組是它的copy版本,使用它的時候需要加上線程鎖铭乾,進(jìn)行線程安全的保護(hù)剪廉。

在RACCommand內(nèi)部,是對NSMutableArray數(shù)組進(jìn)行操作的炕檩,在這里可變數(shù)組里面進(jìn)行增加和刪除的操作斗蒋。


- (void)addActiveExecutionSignal:(RACSignal *)signal {
    NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
    
    @synchronized (self) {
        NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count];
        [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
        [_activeExecutionSignals addObject:signal];
        [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
    }
}


在往數(shù)組里面添加數(shù)據(jù)的時候是滿足KVO的,這里對index進(jìn)行了NSKeyValueChangeInsertion監(jiān)聽笛质。


- (void)removeActiveExecutionSignal:(RACSignal *)signal {
    NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
    
    @synchronized (self) {
        NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) {
            return obj == signal;
        }];
        
        if (indexes.count == 0) return;
        
        [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
        [_activeExecutionSignals removeObjectsAtIndexes:indexes];
        [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)];
    }
}



在移除數(shù)組里面也是依照indexes來進(jìn)行移除的泉沾。注意,增加和刪除的操作都必須包在@synchronized (self)中保證線程安全经瓷。


+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return NO;
}


從上面增加和刪除的操作中我們可以看見了RAC的作者在手動發(fā)送change notification爆哑,手動調(diào)用willChange: 和 didChange:方法。作者的目的在于防止一些不必要的swizzling可能會影響到增加和刪除的操作舆吮,所以這里選擇的手動發(fā)送通知的方式揭朝。

美團(tuán)博客上這篇ReactiveCocoa核心元素與信號流文章里面對activeExecutionSignals的變化引起的一些變化畫了一張數(shù)據(jù)流圖:

除去沒有影響到enabled信號,activeExecutionSignals的變化會影響到其他三個信號色冀。

7. RACSignal *immediateEnabled

這個信號也是一個enabled信號潭袱,但是和之前的enabled信號不同的是,它并不能保證在main thread主線程上锋恬,它可以在任意一個線程上屯换。

8. RACSignal * (^signalBlock)(id input)

這個閉包返回值是一個信號,這個閉包是在初始化RACCommand的時候會用到与学,下面分析源碼的時候會出現(xiàn)彤悔。


- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
- (RACSignal *)execute:(id)input;

RACCommand 暴露出來的就3個方法,2個初始化方法和1個execute:的方法索守,接下來就來分析一下這些方法的底層實現(xiàn)晕窑。

二. initWithEnabled: signalBlock: 底層實現(xiàn)分析

首先先來看看比較短的那個初始化方法。


- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock {
    return [self initWithEnabled:nil signalBlock:signalBlock];
}

initWithSignalBlock:方法實際就是調(diào)用了initWithEnabled: signalBlock:方法卵佛。


- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {

}


initWithSignalBlock:方法相當(dāng)于第一個參數(shù)傳的是nil的initWithEnabled: signalBlock:方法杨赤。第一個參數(shù)是enabledSignal,第二個參數(shù)是signalBlock的閉包截汪。enabledSignal如果傳的是nil疾牲,那么就相當(dāng)于是傳進(jìn)了[RACSignal return:@YES]。

接下來詳細(xì)分析一下initWithEnabled: signalBlock:方法的實現(xiàn)衙解。

這個方法的實現(xiàn)非常長阳柔,需要分段來分析。RACCommand的初始化就是對自己的4個信號蚓峦,executionSignals盔沫,executing医咨,enabled,errors的初始化架诞。

1. executionSignals信號的初始化


RACSignal *newActiveExecutionSignals = [[[[[self rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
                                           
    reduceEach:^(id _, NSDictionary *change) {
    NSArray *signals = change[NSKeyValueChangeNewKey];
    if (signals == nil) return [RACSignal empty];
    
    return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
    }]
   concat]
   publish]
   autoconnect];



通過rac_valuesAndChangesForKeyPath: options: observer: 方法監(jiān)聽self.activeExecutionSignals數(shù)組里面是否有增加新的信號。rac_valuesAndChangesForKeyPath: options: observer: 方法的返回時是一個RACTuple干茉,它的定義是這樣的:RACTuplePack(value, change)谴忧。

只要每次數(shù)組里面加入了新的信號,那么rac_valuesAndChangesForKeyPath: options: observer: 方法就會把新加的值和change字典包裝成RACTuple返回角虫。再對這個信號進(jìn)行一次reduceEach:操作沾谓。

舉個例子,change字典可能是如下的樣子:


{
    indexes = "<_NSCachedIndexSet: 0x60000023b8a0>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
    kind = 2;
    new =     (
        "<RACReplaySubject: 0x6000006613c0> name: "
    );
}


取出change[NSKeyValueChangeNewKey]就能取出每次變化新增的信號數(shù)組戳鹅,然后把這個數(shù)組通過signalWithScheduler:轉(zhuǎn)換成信號均驶。

把原信號中每個值是里面裝滿RACTuple的信號通過變換,變換成了裝滿RACSingnal的三階信號枫虏,通過concat進(jìn)行降階操作妇穴,降階成了二階信號。最后通過publish和autoconnect操作隶债,把冷信號轉(zhuǎn)換成熱信號腾它。

newActiveExecutionSignals最終是一個二階熱信號。

接下來再看看executionSignals是如何變換而來的死讹。


_executionSignals = [[[newActiveExecutionSignals
                       map:^(RACSignal *signal) {
                           return [signal catchTo:[RACSignal empty]];
                       }]
                      deliverOn:RACScheduler.mainThreadScheduler]
                     setNameWithFormat:@"%@ -executionSignals", self];


executionSignals把newActiveExecutionSignals中錯誤信號都換成空信號瞒滴。經(jīng)過map變換之后,executionSignals是newActiveExecutionSignals的無錯誤信號的版本赞警。由于map只是變換并沒有降階妓忍,所以executionSignals還是一個二階的高階冷信號。

注意最后加上了deliverOn愧旦,executionSignals信號每個值都是在主線程中發(fā)送的世剖。

2. errors信號的初始化

在RACCommand中會搜集其所有的error信號,都裝進(jìn)自己的errors的信號中忘瓦。這也是RACCommand的特點(diǎn)之一搁廓,能把錯誤統(tǒng)一處理。


RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals
                                              flattenMap:^(RACSignal *signal) {
                                                  return [[signal ignoreValues]
                                                          catch:^(NSError *error) {
                                                              return [RACSignal return:error];
                                                          }];
                                              }]
                                             deliverOn:RACScheduler.mainThreadScheduler]
                                             publish];

從上面分析中耕皮,我們知道境蜕,newActiveExecutionSignals最終是一個二階熱信號。這里在errorsConnection的變換中凌停,我們對這個二階的熱信號進(jìn)行flattenMap:降階操作粱年,只留下所有的錯誤信號,最后把所有的錯誤信號都裝在一個低階的信號中罚拟,這個信號中每個值都是一個error台诗。同樣完箩,變換中也追加了deliverOn:操作,回到主線程中去操作拉队。最后把這個冷信號轉(zhuǎn)換成熱信號弊知,但是注意,還沒有connect粱快。


_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self];
[errorsConnection connect];

假設(shè)某個訂閱者在RACCommand中的信號已經(jīng)開始執(zhí)行之后才訂閱的秩彤,如果錯誤信號是一個冷信號,那么訂閱之前的錯誤就接收不到了事哭。所以錯誤應(yīng)該是一個熱信號漫雷,不管什么時候訂閱都可以接收到所有的錯誤。

error信號就是熱信號errorsConnection傳出來的一個熱信號鳍咱。error信號每個值都是在主線程上發(fā)送的降盹。

3. executing信號的初始化

executing這個信號表示了當(dāng)前RACCommand是否在執(zhí)行,信號里面的值都是BOOL類型的谤辜。那么如何拿到這樣一個BOOL信號呢蓄坏?


RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) {
    return @(activeSignals.count > 0);
}];


由于self.activeExecutionSignals是可以被KVO的,所以每當(dāng)activeExecutionSignals變化的時候每辟,判斷當(dāng)前數(shù)組里面是否還有信號剑辫,如果數(shù)組里面有值,就代表了當(dāng)前有在執(zhí)行中的信號渠欺。



_executing = [[[[[immediateExecuting
                  deliverOn:RACScheduler.mainThreadScheduler]
                  startWith:@NO]
                  distinctUntilChanged]
                  replayLast]
                  setNameWithFormat:@"%@ -executing", self];



immediateExecuting信號表示當(dāng)前是否有信號在執(zhí)行妹蔽。初始值為NO,一旦immediateExecuting不為NO的時候就會發(fā)出信號挠将。最后通過replayLast轉(zhuǎn)換成永遠(yuǎn)只保存最新的一個值的熱信號胳岂。

executing信號除去第一個默認(rèn)值NO,其他的每個值也是在主線程中發(fā)送的舔稀。

4. enabled信號的初始化


RACSignal *moreExecutionsAllowed = [RACSignal
                                    if:RACObserve(self, allowsConcurrentExecution)
                                    then:[RACSignal return:@YES]
                                    else:[immediateExecuting not]];


先監(jiān)聽self.allowsConcurrentExecution變量是否有變化乳丰,allowsConcurrentExecution默認(rèn)值為NO。如果有變化内贮,allowsConcurrentExecution為YES产园,就說明允許并發(fā)執(zhí)行,那么就返回YES的RACSignal夜郁,allowsConcurrentExecution為NO什燕,就說明不允許并發(fā)執(zhí)行,那么就要看當(dāng)前是否有正在執(zhí)行的信號竞端。immediateExecuting就是代表當(dāng)前是否有在執(zhí)行的信號屎即,對這個信號取非,就是是否允許執(zhí)行下一個信號的BOOL值。這就是moreExecutionsAllowed的信號技俐。


if (enabledSignal == nil) {
    enabledSignal = [RACSignal return:@YES];
} else {
    enabledSignal = [[[enabledSignal
                       startWith:@YES]
                       takeUntil:self.rac_willDeallocSignal]
                       replayLast];
}


這里的代碼就說明了乘陪,如果第一個參數(shù)傳的是nil,那么就相當(dāng)于傳進(jìn)來了一個[RACSignal return:@YES]信號雕擂。

如果enabledSignal不為nil啡邑,就在enabledSignal信號前面插入一個YES的信號,目的是為了防止傳入的enabledSignal雖然不為nil捂刺,但是里面是沒有信號的谣拣,比如[RACSignal never],[RACSignal empty]族展,這些信號傳進(jìn)來也相當(dāng)于是沒用的拔鹰,所以在開頭加一個YES的初始值信號仪缸。

最后同樣通過replayLast操作轉(zhuǎn)換成只保存最新的一個值的熱信號。


_immediateEnabled = [[RACSignal
                      combineLatest:@[ enabledSignal, moreExecutionsAllowed ]]
                      and];

這里涉及到了combineLatest:的變換操作列肢,這個操作在之前的文章里面分析過了恰画,這里不再詳細(xì)分析源碼實現(xiàn)。combineLatest:的作用就是把后面數(shù)組里面?zhèn)魅氲拿總€信號瓷马,不管是誰發(fā)送出來一個信號拴还,都會把數(shù)組里面所有信號的最新的值組合到一個RACTuple里面。immediateEnabled會把每個RACTuple里面的元素都進(jìn)行邏輯and運(yùn)算欧聘,這樣immediateEnabled信號里面裝的也都是BOOL值了片林。

immediateEnabled信號的意義就是每時每刻監(jiān)聽RACCommand是否可以enabled。它是由2個信號進(jìn)行and操作得來的怀骤。每當(dāng)allowsConcurrentExecution變化的時候就會產(chǎn)生一個信號费封,此時再加上enabledSignal信號,就能判斷這一刻RACCommand是否能夠enabled蒋伦。每當(dāng)enabledSignal變化的時候也會產(chǎn)生一個信號弓摘,再加上allowsConcurrentExecution是否允許并發(fā),也能判斷這一刻RACCommand是否能夠enabled痕届。所以immediateEnabled是由這兩個信號combineLatest:之后再進(jìn)行and操作得來的韧献。



_enabled = [[[[[self.immediateEnabled
                take:1]
                concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]]
                distinctUntilChanged]
                replayLast]
                setNameWithFormat:@"%@ -enabled", self];


由上面源碼可以知道,self.immediateEnabled是由enabledSignal, moreExecutionsAllowed組合而成的研叫。根據(jù)源碼锤窑,enabledSignal的第一個信號值一定是[RACSignal return:@YES],moreExecutionsAllowed是RACObserve(self, allowsConcurrentExecution)產(chǎn)生的蓝撇,由于allowsConcurrentExecution默認(rèn)值是NO果复,所以moreExecutionsAllowed的第一個值是[immediateExecuting not]。

這里比較奇怪的地方是為何要用一次concat操作渤昌,把第一個信號值和后面的連接起來虽抄。如果直接寫[self.immediateEnabled deliverOn:RACScheduler.mainThreadScheduler]走搁,那么整個self.immediateEnabled就都在主線程上了。作者既然沒有這么寫迈窟,肯定是有原因的击碗。

This signal will send its current value upon subscription, and then all future values on the main thread.

通過查看文檔,明白了作者的意圖锄贼,作者的目的是為了讓第一個值以后的每個值都發(fā)送在主線程上榴都,所以這里skip:1之后接著deliverOn:RACScheduler.mainThreadScheduler。那第一個值呢湖员?第一個值在一訂閱的時候就發(fā)送出去了贫悄,同訂閱者所在線程一致。

distinctUntilChanged保證enabled信號每次狀態(tài)變化的時候只取到一個狀態(tài)值娘摔。最后調(diào)用replayLast轉(zhuǎn)換成只保存最新值的熱信號窄坦。

從源碼上看,enabled信號除去第一個值以外的每個值也都是在主線程上發(fā)送的凳寺。

三. execute:底層實現(xiàn)分析


- (RACSignal *)execute:(id)input {
    // 1
    BOOL enabled = [[self.immediateEnabled first] boolValue];
    if (!enabled) {
        NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
                          NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),RACUnderlyingCommandErrorKey: self }];
        
        return [RACSignal error:error];
    }
    // 2
    RACSignal *signal = self.signalBlock(input);
    NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
    // 3
    RACMulticastConnection *connection = [[signal subscribeOn:RACScheduler.mainThreadScheduler] multicast:[RACReplaySubject subject]];
    
    @weakify(self);
    // 4
    [self addActiveExecutionSignal:connection.signal];
    [connection.signal subscribeError:^(NSError *error) {
        @strongify(self);
        // 5
        [self removeActiveExecutionSignal:connection.signal];
    } completed:^{
        @strongify(self);
        // 5
        [self removeActiveExecutionSignal:connection.signal];
    }];
    
    [connection connect];
     // 6
    return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]];
}



把上述代碼分成6步來分析:

  1. self.immediateEnabled為了保證第一個值能正常的發(fā)送給訂閱者鸭津,所以這里用了同步的first的方法,也是可以接受的肠缨。調(diào)用了first方法之后逆趋,根據(jù)這第一個值來判斷RACCommand是否可以開始執(zhí)行。如果不能執(zhí)行就返回一個錯誤信號晒奕。

  2. 這里就是RACCommand開始執(zhí)行的地方闻书。self.signalBlock是RACCommand在初始化的時候傳入的一個參數(shù),RACSignal * (^signalBlock)(id input)這個閉包的入?yún)⑹且粋€id input吴汪,返回值是一個信號惠窄。這里正好把execute的入?yún)nput傳進(jìn)來。

  3. 把RACCommand執(zhí)行之后的信號先調(diào)用subscribeOn:保證didSubscribe block( )閉包在主線程中執(zhí)行漾橙,再轉(zhuǎn)換成RACMulticastConnection杆融,準(zhǔn)備轉(zhuǎn)換成熱信號。

  4. 在最終的信號被訂閱者訂閱之前霜运,我們需要優(yōu)先更新RACCommand里面的executing和enabled信號脾歇,所以這里要先把connection.signal加入到self.activeExecutionSignals數(shù)組里面。

  5. 訂閱最終結(jié)果信號淘捡,出現(xiàn)錯誤或者完成藕各,都要更新self.activeExecutionSignals數(shù)組。

  6. 這里想說明的是焦除,最終的execute:返回的信號激况,和executionSignals是一樣的。

這里有一個需要注意的點(diǎn):

executionSignals雖然是一個冷信號,但是它是由內(nèi)部的addedExecutionSignalsSubject的產(chǎn)生的乌逐,這是一個熱信號竭讳,訂閱者訂閱它的時候需要在execute:執(zhí)行之前去訂閱,否則這個addedExecutionSignalsSubject熱信號對已保存的所有的訂閱者發(fā)送完信號以后浙踢,再訂閱就收不到任何信號了绢慢。所以需要在熱信號發(fā)送信號之前訂閱,把自己保存到熱信號的訂閱者數(shù)組里洛波。所以executionSignals的訂閱要在execute:執(zhí)行之前胰舆。

而execute:返回的信號是RACReplaySubject熱信號,它會把訂閱者保存起來蹬挤,即使先發(fā)送信號缚窿,再訂閱,訂閱者也可以收到之前發(fā)送的值焰扳。

兩個信號雖然信號內(nèi)容都相同滨攻,但是訂閱的先后不同,executionSignals必須在execute:執(zhí)行之前去訂閱蓝翰,而execute:返回的信號是在execute:執(zhí)行之后去訂閱的。

四. RACCommand的一些Category

RACCommand在日常iOS開發(fā)過程中女嘲,很適合上下拉刷新畜份,按鈕點(diǎn)擊等操作,所以ReactiveCocoa就幫我們在這些UI控件上封裝了一個RACCommand屬性——rac_command欣尼。

1. UIBarButtonItem+RACCommandSupport

一旦UIBarButtonItem被點(diǎn)擊爆雹,RACCommand就會執(zhí)行。


- (RACCommand *)rac_command {
    return objc_getAssociatedObject(self, UIControlRACCommandKey);
}

- (void)setRac_command:(RACCommand *)command {
    objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    // 檢查已經(jīng)存儲過的信號愕鼓,移除老的钙态,添加一個新的
    RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey);
    [disposable dispose];
    
    if (command == nil) return;
    
    disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
    objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [self rac_hijackActionAndTargetIfNeeded];
}


給UIBarButtonItem添加rac_command屬性用到了runtime里面的AssociatedObject關(guān)聯(lián)對象。這里給UIBarButtonItem類新增了2個關(guān)聯(lián)對象菇晃,key分別是UIControlRACCommandKey册倒,UIControlEnabledDisposableKey。UIControlRACCommandKey對應(yīng)的是綁定的command磺送,UIControlEnabledDisposableKey對應(yīng)的是command.enabled的disposable信號驻子。

set方法里面最后會調(diào)用rac_hijackActionAndTargetIfNeeded,這個方法需要特別注意:


- (void)rac_hijackActionAndTargetIfNeeded {
    SEL hijackSelector = @selector(rac_commandPerformAction:);
    if (self.target == self && self.action == hijackSelector) return;
    
    if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action.");
        
        self.target = self;
        self.action = hijackSelector;
}

- (void)rac_commandPerformAction:(id)sender {
    [self.rac_command execute:sender];
}



rac_hijackActionAndTargetIfNeeded方法是對當(dāng)前UIBarButtonItem的target和action進(jìn)行檢查估灿。

如果當(dāng)前UIBarButtonItem的target = self崇呵,并且action = @selector(rac_commandPerformAction:),那么就算檢查通過符合執(zhí)行RACCommand的前提條件了馅袁,直接return域慷。

如果上述條件不符合,就強(qiáng)制改變UIBarButtonItem的target = self,并且action = @selector(rac_commandPerformAction:)犹褒,所以這里需要注意的就是抵窒,UIBarButtonItem調(diào)用rac_command,會被強(qiáng)制改變它的target和action化漆。

2. UIButton+RACCommandSupport

一旦UIButton被點(diǎn)擊估脆,RACCommand就會執(zhí)行。


- (RACCommand *)rac_command {
    return objc_getAssociatedObject(self, UIButtonRACCommandKey);
}

- (void)setRac_command:(RACCommand *)command {
    objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey);
    [disposable dispose];
    
    if (command == nil) return;
    
    disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
    objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [self rac_hijackActionAndTargetIfNeeded];
}


這里給UIButton添加綁定2個屬性同樣也用到了runtime里面的AssociatedObject關(guān)聯(lián)對象座云。代碼和UIBarButtonItem的實現(xiàn)基本一樣疙赠。同樣是給UIButton類新增了2個關(guān)聯(lián)對象,key分別是UIButtonRACCommandKey朦拖,UIButtonEnabledDisposableKey圃阳。UIButtonRACCommandKey對應(yīng)的是綁定的command,UIButtonEnabledDisposableKey對應(yīng)的是command.enabled的disposable信號璧帝。



- (void)rac_hijackActionAndTargetIfNeeded {
    SEL hijackSelector = @selector(rac_commandPerformAction:);
    
    for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) {
        if (hijackSelector == NSSelectorFromString(selector)) {
            return;
        }
    }
    
    [self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside];
}

- (void)rac_commandPerformAction:(id)sender {
    [self.rac_command execute:sender];
}



rac_hijackActionAndTargetIfNeeded函數(shù)的意思和之前的一樣捍岳,也是檢查UIButton的target和action。最終結(jié)果的UIButton的target = self睬隶,action = @selector(rac_commandPerformAction:)

3. UIRefreshControl+RACCommandSupport


- (RACCommand *)rac_command {
    return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey);
}

- (void)setRac_command:(RACCommand *)command {
    objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    [objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose];
    
    if (command == nil) return;
    
    RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
    
    RACDisposable *executionDisposable = [[[[self
                                             rac_signalForControlEvents:UIControlEventValueChanged]
                                             map:^(UIRefreshControl *x) {
                                                return [[[command
                                                          execute:x]
                                                          catchTo:[RACSignal empty]]
                                                          then:^{
                                                            return [RACSignal return:x];
                                                        }];
                                            }]
                                            concat]
                                            subscribeNext:^(UIRefreshControl *x) {
                                              [x endRefreshing];
                                            }];
    
    RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]];
    objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}



這里給UIRefreshControl添加綁定2個屬性同樣也用到了runtime里面的AssociatedObject關(guān)聯(lián)對象锣夹。代碼和UIBarButtonItem的實現(xiàn)基本一樣。同樣是給UIButton類新增了2個關(guān)聯(lián)對象苏潜,key分別是UIRefreshControlRACCommandKey银萍,UIRefreshControlDisposableKey。UIRefreshControlRACCommandKey對應(yīng)的是綁定的command恤左,UIRefreshControlDisposableKey對應(yīng)的是command.enabled的disposable信號贴唇。

這里多了一個executionDisposable信號,這個信號是用來結(jié)束刷新操作的飞袋。


[[[command execute:x] catchTo:[RACSignal empty]] then:^{ return [RACSignal return:x]; }];

這個信號變換先把RACCommand執(zhí)行戳气,執(zhí)行之后得到的結(jié)果信號剔除掉所有的錯誤。then操作就是忽略掉所有值巧鸭,在最后添加一個返回UIRefreshControl對象的信號瓶您。

[self rac_signalForControlEvents:UIControlEventValueChanged]之后再map升階為高階信號,所以最后用concat降階蹄皱。最后訂閱這個信號览闰,訂閱只會收到一個值,command執(zhí)行完畢之后的信號發(fā)送完所有的值的時候巷折,即收到這個值的時刻就是最終刷新結(jié)束的時刻压鉴。

所以最終的disposable信號還要加上executionDisposable。

最后

關(guān)于RACCommand底層實現(xiàn)分析都已經(jīng)分析完成锻拘。最后請大家多多指教油吭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末击蹲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子婉宰,更是在濱河造成了極大的恐慌歌豺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件心包,死亡現(xiàn)場離奇詭異类咧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蟹腾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門痕惋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人娃殖,你說我怎么就攤上這事值戳。” “怎么了炉爆?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵堕虹,是天一觀的道長。 經(jīng)常有香客問我芬首,道長赴捞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任郁稍,我火速辦了婚禮螟炫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艺晴。我一直安慰自己,他們只是感情好掸屡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布封寞。 她就那樣靜靜地躺著,像睡著了一般仅财。 火紅的嫁衣襯著肌膚如雪狈究。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天盏求,我揣著相機(jī)與錄音抖锥,去河邊找鬼。 笑死碎罚,一個胖子當(dāng)著我的面吹牛磅废,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荆烈,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼拯勉,長吁一口氣:“原來是場噩夢啊……” “哼竟趾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宫峦,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岔帽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犀勒,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年妥曲,在試婚紗的時候發(fā)現(xiàn)自己被綠了贾费。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡逾一,死狀恐怖铸本,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遵堵,我是刑警寧澤箱玷,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站陌宿,受9級特大地震影響锡足,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壳坪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一舶得、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧爽蝴,春花似錦沐批、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至发框,卻和暖如春躺彬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梅惯。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工宪拥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铣减。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓她君,卻偏偏與公主長得像,于是被迫代替她去往敵國和親葫哗。 傳聞我的和親對象是個殘疾皇子犁河,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容