ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

一縷殤流化隱半邊冰霜發表於2016-12-06

前言

關於ReactiveCocoa v2.5中冷訊號和熱訊號的文章中,最著名的就是美團的臧成威老師寫的3篇冷熱訊號的文章:

細說ReactiveCocoa的冷訊號與熱訊號(一)
細說ReactiveCocoa的冷訊號與熱訊號(二):為什麼要區分冷熱訊號
細說ReactiveCocoa的冷訊號與熱訊號(三):怎麼處理冷訊號與熱訊號

由於最近在寫關於RACSignal底層實現分析的文章,當然也逃不了關於冷熱訊號操作的分析。這篇文章打算分析分析如何從冷訊號轉成熱訊號的底層實現。

目錄

  • 1.關於冷訊號和熱訊號的概念
  • 2.RACSignal熱訊號
  • 3.RACSignal冷訊號
  • 4.冷訊號是如何轉換成熱訊號的

一. 關於冷訊號和熱訊號的概念

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

冷熱訊號的概念是源自於源於.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,

Hot Observable是主動的,儘管你並沒有訂閱事件,但是它會時刻推送,就像滑鼠移動;而Cold Observable是被動的,只有當你訂閱的時候,它才會釋出訊息。

Hot Observable可以有多個訂閱者,是一對多,集合可以與訂閱者共享資訊;而Cold Observable只能一對一,當有不同的訂閱者,訊息是重新完整傳送。

在這篇文章細說ReactiveCocoa的冷訊號與熱訊號(一)詳細分析了冷熱訊號的特點:

熱訊號是主動的,即使你沒有訂閱事件,它仍然會時刻推送。而冷訊號是被動的,只有當你訂閱的時候,它才會傳送訊息。

熱訊號可以有多個訂閱者,是一對多,訊號可以與訂閱者共享資訊。而冷訊號只能一對一,當有不同的訂閱者,訊息會從新完整傳送。

二. RACSignal熱訊號

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

RACSignal家族中符合熱訊號的特點的訊號有以下幾個。

1.RACSubject



@interface RACSubject : RACSignal <RACSubscriber>

@property (nonatomic, strong, readonly) NSMutableArray *subscribers;
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block;
+ (instancetype)subject;

@end複製程式碼

首先來看看RACSubject的定義。

RACSubject是繼承自RACSignal,並且它還遵守RACSubscriber協議。這就意味著它既能訂閱訊號,也能傳送訊號。

在RACSubject裡面有一個NSMutableArray陣列,裡面裝著該訊號的所有訂閱者。其次還有一個RACCompoundDisposable訊號,裡面裝著該訊號所有訂閱者的RACDisposable。

RACSubject之所以能稱之為熱訊號,那麼它肯定是符合上述熱訊號的定義的。讓我們從它的實現來看看它是如何符合的。



- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }

    return [RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }];
}複製程式碼

上面是RACSubject的實現,它和RACSignal最大的不同在這兩行


NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
    [subscribers addObject:subscriber];
}複製程式碼

RACSubject 把它的所有訂閱者全部都儲存到了NSMutableArray的陣列裡。既然儲存了所有的訂閱者,那麼sendNext,sendError,sendCompleted就需要發生改變。



- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}

- (void)sendError:(NSError *)error {
    [self.disposable dispose];

    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendError:error];
    }];
}

- (void)sendCompleted {
    [self.disposable dispose];

    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendCompleted];
    }];
}複製程式碼

從原始碼可以看到,RACSubject中的sendNext,sendError,sendCompleted都會執行enumerateSubscribersUsingBlock:方法。


- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id<RACSubscriber> subscriber in subscribers) {
        block(subscriber);
    }
}複製程式碼

enumerateSubscribersUsingBlock:方法會取出所有RACSubject的訂閱者,依次呼叫入參的block( )方法。

關於RACSubject的訂閱和傳送的流程可以參考第一篇文章,大體一致,其他的不同就是會依次對自己的訂閱者傳送訊號。

RACSubject就滿足了熱訊號的特點,它即使沒有訂閱者,因為自己繼承了RACSubscriber協議,所以自己本身就可以傳送訊號。冷訊號只能被訂閱了才能傳送訊號。

RACSubject可以有很多訂閱者,它也會把這些訂閱者都儲存到自己的陣列裡。RACSubject之後再傳送訊號,訂閱者就如同一起看電視,播放過的節目就看不到了,傳送過的訊號也接收不到了。接收訊號。而RACSignal傳送訊號,訂閱者接收訊號都只能從頭開始接受,如同看點播節目,每次看都從頭開始看。

2. RACGroupedSignal


@interface RACGroupedSignal : RACSubject

@property (nonatomic, readonly, copy) id<NSCopying> key;
+ (instancetype)signalWithKey:(id<NSCopying>)key;
@end複製程式碼

先看看RACGroupedSignal的定義。

RACGroupedSignal是在RACsignal這個方法裡面被用到的。


- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock複製程式碼

在這個方法裡面,sendNext裡面最後裡面是由RACGroupedSignal傳送訊號。


[groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x];複製程式碼

關於groupBy的詳細分析請看這篇文章

3. RACBehaviorSubject


@interface RACBehaviorSubject : RACSubject
@property (nonatomic, strong) id currentValue;
+ (instancetype)behaviorSubjectWithDefaultValue:(id)value;
@end複製程式碼

這個訊號裡面儲存了一個物件currentValue,這裡儲存著這個訊號的最新的值。

當然也可以呼叫類方法behaviorSubjectWithDefaultValue


+ (instancetype)behaviorSubjectWithDefaultValue:(id)value {
    RACBehaviorSubject *subject = [self subject];
    subject.currentValue = value;
    return subject;
}複製程式碼

在這個方法裡面儲存預設的值,如果RACBehaviorSubject沒有接受到任何值,那麼這個訊號就會傳送這個預設的值。

當RACBehaviorSubject被訂閱:


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACDisposable *subscriptionDisposable = [super subscribe:subscriber];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            [subscriber sendNext:self.currentValue];
        }
    }];

    return [RACDisposable disposableWithBlock:^{
        [subscriptionDisposable dispose];
        [schedulingDisposable dispose];
    }];
}複製程式碼

sendNext裡面會始終傳送儲存的currentValue值。呼叫sendNext會呼叫RACSubject裡面的sendNext,也會依次傳送訊號值給訂閱陣列裡面每個訂閱者。

當RACBehaviorSubject向訂閱者sendNext的時候:


- (void)sendNext:(id)value {
    @synchronized (self) {
        self.currentValue = value;
        [super sendNext:value];
    }
}複製程式碼

RACBehaviorSubject會把傳送的值更新到currentValue裡面。下次傳送值就會傳送最後更新的值。

4. RACReplaySubject



const NSUInteger RACReplaySubjectUnlimitedCapacity = NSUIntegerMax;
@interface RACReplaySubject : RACSubject

@property (nonatomic, assign, readonly) NSUInteger capacity;
@property (nonatomic, strong, readonly) NSMutableArray *valuesReceived;
@property (nonatomic, assign) BOOL hasCompleted;
@property (nonatomic, assign) BOOL hasError;
@property (nonatomic, strong) NSError *error;
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;

@end複製程式碼

RACReplaySubject中會儲存RACReplaySubjectUnlimitedCapacity大小的歷史值。



+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
    return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}

- (instancetype)init {
    return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}

- (instancetype)initWithCapacity:(NSUInteger)capacity {
    self = [super init];
    if (self == nil) return nil;

    _capacity = capacity;
    _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);

    return self;
}複製程式碼

在RACReplaySubject初始化中會初始化一個capacity大小的陣列。


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];

    RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
        @synchronized (self) {
            for (id value in self.valuesReceived) {
                if (compoundDisposable.disposed) return;

                [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
            }

            if (compoundDisposable.disposed) return;

            if (self.hasCompleted) {
                [subscriber sendCompleted];
            } else if (self.hasError) {
                [subscriber sendError:self.error];
            } else {
                RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
                [compoundDisposable addDisposable:subscriptionDisposable];
            }
        }
    }];

    [compoundDisposable addDisposable:schedulingDisposable];

    return compoundDisposable;
}複製程式碼

當RACReplaySubject被訂閱的時候,會把valuesReceived陣列裡面的值都傳送出去。



- (void)sendNext:(id)value {
    @synchronized (self) {
        [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
        [super sendNext:value];

        if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
            [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
        }
    }
}複製程式碼

在sendNext中,valuesReceived會儲存每次接收到的值。呼叫super的sendNext,會依次把值都傳送到每個訂閱者中。

這裡還會判斷陣列裡面儲存了多少個值。如果儲存的值的個數大於了capacity,那麼要移除掉陣列裡面從0開始的前幾個值,保證陣列裡面只裝capacity個數的值。

RACReplaySubject 和 RACSubject 的區別在於,RACReplaySubject還會把歷史的訊號值都儲存起來傳送給訂閱者。這一點,RACReplaySubject更像是RACSingnal 和 RACSubject 的合體版。RACSignal是冷訊號,一旦被訂閱就會向訂閱者傳送所有的值,這一點RACReplaySubject和RACSignal是一樣的。但是RACReplaySubject又有著RACSubject的特性,會把所有的值傳送給多個訂閱者。當RACReplaySubject傳送完之前儲存的歷史值之後,之後再傳送訊號的行為就和RACSubject完全一致了。

三. RACSignal冷訊號

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

在ReactiveCocoa v2.5中除了RACsignal訊號以外,還有一些特殊的冷訊號。

1.RACEmptySignal


@interface RACEmptySignal : RACSignal
+ (RACSignal *)empty;
@end複製程式碼

這個訊號只有一個empty方法。


+ (RACSignal *)empty {
#ifdef DEBUG
    return [[[self alloc] init] setNameWithFormat:@"+empty"];
#else
    static id singleton;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        singleton = [[self alloc] init];
    });

    return singleton;
#endif
}複製程式碼

在debug模式下,返回一個名字叫empty的訊號。在release模式下,返回一個單例的empty訊號。


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);
    return [RACScheduler.subscriptionScheduler schedule:^{
        [subscriber sendCompleted];
    }];
}複製程式碼

RACEmptySignal訊號一旦被訂閱就會傳送sendCompleted。

2. RACReturnSignal



@interface RACReturnSignal : RACSignal
@property (nonatomic, strong, readonly) id value;
+ (RACSignal *)return:(id)value;
@end複製程式碼

RACReturnSignal訊號的定義也很簡單,直接根據value的值返回一個RACSignal。



+ (RACSignal *)return:(id)value {
#ifndef DEBUG
    if (value == RACUnit.defaultUnit) {
        static RACReturnSignal *unitSingleton;
        static dispatch_once_t unitPred;

        dispatch_once(&unitPred, ^{
            unitSingleton = [[self alloc] init];
            unitSingleton->_value = RACUnit.defaultUnit;
        });

        return unitSingleton;
    } else if (value == nil) {
        static RACReturnSignal *nilSingleton;
        static dispatch_once_t nilPred;

        dispatch_once(&nilPred, ^{
            nilSingleton = [[self alloc] init];
            nilSingleton->_value = nil;
        });

        return nilSingleton;
    }
#endif

    RACReturnSignal *signal = [[self alloc] init];
    signal->_value = value;

#ifdef DEBUG
    [signal setNameWithFormat:@"+return: %@", value];
#endif

    return signal;
}複製程式碼

在debug模式下直接新建一個RACReturnSignal訊號裡面的值儲存的是入參value。在release模式下,會依照value的值是否是空,來新建對應的單例RACReturnSignal。


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

 return [RACScheduler.subscriptionScheduler schedule:^{
    [subscriber sendNext:self.value];
    [subscriber sendCompleted];
 }];
}複製程式碼

RACReturnSignal在被訂閱的時候,就只會傳送一個value值的訊號,傳送完畢之後就sendCompleted。

3. RACDynamicSignal

這個訊號是建立RACSignal createSignal:的真身。關於RACDynamicSignal詳細過程請看第一篇文章

4. RACErrorSignal


@interface RACErrorSignal : RACSignal
@property (nonatomic, strong, readonly) NSError *error;
+ (RACSignal *)error:(NSError *)error;
@end複製程式碼

RACErrorSignal訊號裡面就儲存了一個NSError。


+ (RACSignal *)error:(NSError *)error {
    RACErrorSignal *signal = [[self alloc] init];
    signal->_error = error;

#ifdef DEBUG
    [signal setNameWithFormat:@"+error: %@", error];
#else
    signal.name = @"+error:";
#endif

    return signal;
}複製程式碼

RACErrorSignal初始化的時候把外界傳進來的Error儲存起來。當被訂閱的時候就傳送這個Error出去。

5. RACChannelTerminal


@interface RACChannelTerminal : RACSignal <RACSubscriber>

- (id)init __attribute__((unavailable("Instantiate a RACChannel instead")));

@property (nonatomic, strong, readonly) RACSignal *values;
@property (nonatomic, strong, readonly) id<RACSubscriber> otherTerminal;
- (id)initWithValues:(RACSignal *)values otherTerminal:(id<RACSubscriber>)otherTerminal;

@end複製程式碼

RACChannelTerminal在RAC日常開發中,用來雙向繫結的。它和RACSubject一樣,既繼承自RACSignal,同樣又遵守RACSubscriber協議。雖然具有RACSubject的傳送和接收訊號的特性,但是它依舊是冷訊號,因為它無法一對多,它傳送訊號還是隻能一對一。

RACChannelTerminal無法手動初始化,需要靠RACChannel去初始化。


- (id)init {
    self = [super init];
    if (self == nil) return nil;

    RACReplaySubject *leadingSubject = [[RACReplaySubject replaySubjectWithCapacity:0] setNameWithFormat:@"leadingSubject"];
    RACReplaySubject *followingSubject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"followingSubject"];

    [[leadingSubject ignoreValues] subscribe:followingSubject];
    [[followingSubject ignoreValues] subscribe:leadingSubject];

    _leadingTerminal = [[[RACChannelTerminal alloc] initWithValues:leadingSubject otherTerminal:followingSubject] setNameWithFormat:@"leadingTerminal"];
    _followingTerminal = [[[RACChannelTerminal alloc] initWithValues:followingSubject otherTerminal:leadingSubject] setNameWithFormat:@"followingTerminal"];

    return self;
}複製程式碼

在RACChannel的初始化中會呼叫RACChannelTerminal的initWithValues:方法,這裡的入參都是RACReplaySubject型別的。所以訂閱RACChannelTerminal過程的時候:


- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    return [self.values subscribe:subscriber];
}複製程式碼

self.values其實就是一個RACReplaySubject,就相當於訂閱RACReplaySubject。訂閱過程同上面RACReplaySubject的訂閱過程。


- (void)sendNext:(id)value {
    [self.otherTerminal sendNext:value];
}

- (void)sendError:(NSError *)error {
    [self.otherTerminal sendError:error];
}

- (void)sendCompleted {
    [self.otherTerminal sendCompleted];
}複製程式碼

self.otherTerminal也是RACReplaySubject型別的,RACChannelTerminal管道兩邊都是RACReplaySubject型別的訊號。當RACChannelTerminal開始sendNext,sendError,sendCompleted是呼叫的管道另外一個的RACReplaySubject進行這些對應的操作的。

平時使用RACChannelTerminal的地方在View和ViewModel的雙向繫結上面。

例如在登入介面,輸入密碼文字框TextField和ViewModel的Password雙向繫結



    RACChannelTerminal *passwordTerminal = [_passwordTextField rac_newTextChannel];
    RACChannelTerminal *viewModelPasswordTerminal = RACChannelTo(_viewModel, password);
    [viewModelPasswordTerminal subscribe:passwordTerminal];
    [passwordTerminal subscribe:viewModelPasswordTerminal];複製程式碼

雙向繫結的兩個訊號都會因為對方的改變而收到新的訊號。

至此所有的RACSignal的分類就都理順了,按照冷訊號和熱訊號的分類也分好了。

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

四. 冷訊號是如何轉換成熱訊號的

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

為何有時候需要把冷訊號轉換成熱訊號呢?詳情可以看這篇文章裡面舉的例子:細說ReactiveCocoa的冷訊號與熱訊號(二):為什麼要區分冷熱訊號

根據RACSignal訂閱和傳送訊號的流程,我們可以知道,每訂閱一次冷訊號RACSignal,就會執行一次didSubscribe閉包。這個時候就是可能出現問題的地方。如果RACSignal是被用於網路請求,那麼在didSubscribe閉包裡面會被重複的請求。上面文中提到了訊號被訂閱了6次,網路請求也會請求6次。這並不是我們想要的。網路請求只需要請求1次。

如何做到訊號只執行一次didSubscribe閉包,最重要的一點是RACSignal冷訊號只能被訂閱一次。由於冷訊號只能一對一,那麼想一對多就只能交給熱訊號去處理了。這時候就需要把冷訊號轉換成熱訊號。

在ReactiveCocoa v2.5中,冷訊號轉換成熱訊號需要用到RACMulticastConnection 這個類。



@interface RACMulticastConnection : NSObject
@property (nonatomic, strong, readonly) RACSignal *signal;
- (RACDisposable *)connect;
- (RACSignal *)autoconnect;
@end


@interface RACMulticastConnection () {
    RACSubject *_signal;
    int32_t volatile _hasConnected;
}
@property (nonatomic, readonly, strong) RACSignal *sourceSignal;
@property (strong) RACSerialDisposable *serialDisposable;
@end複製程式碼

看看RACMulticastConnection類的定義。最主要的是儲存了兩個訊號,一個是RACSubject,一個是sourceSignal(RACSignal型別)。在.h中暴露給外面的是RACSignal,在.m中實際使用的是RACSubject。看它的定義就能猜到接下去它會做什麼:用sourceSignal去傳送訊號,內部再用RACSubject去訂閱sourceSignal,然後RACSubject會把sourceSignal的訊號值依次發給它的訂閱者們。

ReactiveCocoa 中 RACSignal 冷訊號和熱訊號底層實現分析

用一個不恰當的比喻來形容RACMulticastConnection,它就像上圖中心的那個“地球”,“地球”就是訂閱了sourceSignal的RACSubject,RACSubject把值傳送給各個“連線”者(訂閱者)。sourceSignal只有內部的RACSubject一個訂閱者,所以就完成了我們只想執行didSubscribe閉包一次,但是能把值傳送給各個訂閱者的願望。

在看看RACMulticastConnection的初始化


- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source != nil);
    NSCParameterAssert(subject != nil);

    self = [super init];
    if (self == nil) return nil;

    _sourceSignal = source;
    _serialDisposable = [[RACSerialDisposable alloc] init];
    _signal = subject;

    return self;
}複製程式碼

初始化方法就是把外界傳進來的RACSignal儲存成sourceSignal,把外界傳進來的RACSubject儲存成自己的signal屬性。

RACMulticastConnection有兩個連線方法。



- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }
    return self.serialDisposable;
}複製程式碼

這裡出現了一個不多見的函式OSAtomicCompareAndSwap32Barrier,它是原子運算的操作符,主要用於Compare and swap,原型如下:


bool    OSAtomicCompareAndSwap32Barrier( int32_t __oldValue, int32_t __newValue, volatile int32_t *__theValue );複製程式碼

關鍵字volatile只確保每次獲取volatile變數時都是從記憶體載入變數,而不是使用暫存器裡面的值,但是它不保證程式碼訪問變數是正確的。

如果用虛擬碼去實現這個函式:


f (*__theValue == __oldValue) {  
    *__theValue = __newValue;  
    return 1;  
} else {  
    return 0;  
}複製程式碼

如果_hasConnected為0,意味著沒有連線,OSAtomicCompareAndSwap32Barrier返回1,shouldConnect就應該連線。如果_hasConnected為1,意味著已經連線過了,OSAtomicCompareAndSwap32Barrier返回0,shouldConnect不會再次連線。

所謂連線的過程就是RACMulticastConnection內部用RACSubject訂閱self.sourceSignal。sourceSignal是RACSignal,會把訂閱者RACSubject儲存到RACPassthroughSubscriber中,sendNext的時候就會呼叫RACSubject sendNext,這時就會把sourceSignal的訊號都傳送給各個訂閱者了。


- (RACSignal *)autoconnect {
    __block volatile int32_t subscriberCount = 0;

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            OSAtomicIncrement32Barrier(&subscriberCount);

            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
            RACDisposable *connectionDisposable = [self connect];

            return [RACDisposable disposableWithBlock:^{
                [subscriptionDisposable dispose];

                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
                    [connectionDisposable dispose];
                }
            }];
        }]
        setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}複製程式碼

OSAtomicIncrement32Barrier 和 OSAtomicDecrement32Barrier也是原子運算的操作符,分別是+1和-1操作。在autoconnect為了保證執行緒安全,用到了一個subscriberCount的類似訊號量的volatile變數,保證第一個訂閱者能連線上。返回的新的訊號的訂閱者訂閱RACSubject,RACSubject也會去訂閱內部的sourceSignal。

把冷訊號轉換成熱訊號用以下5種方式,5種方法都會用到RACMulticastConnection。接下來一一分析它們的具體實現。

1. multicast:


- (RACMulticastConnection *)multicast:(RACSubject *)subject {
    [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
    return connection;
}複製程式碼

multicast:的操作就是初始化一個RACMulticastConnection物件,SourceSignal是self,內部的RACSubject是入參subject。


    RACMulticastConnection *connection = [signal multicast:[RACSubject subject]];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [connection connect];複製程式碼

呼叫 multicast:把冷訊號轉換成熱訊號有一個點不方便的是,需要自己手動connect。注意轉換完之後的熱訊號在RACMulticastConnection的signal屬性中,所以需要訂閱的是connection.signal。

2. publish


- (RACMulticastConnection *)publish {
    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
    RACMulticastConnection *connection = [self multicast:subject];
    return connection;
}複製程式碼

publish方法只不過是去呼叫了multicast:方法,publish內部會新建好一個RACSubject,並把它當成入參傳遞給RACMulticastConnection。


    RACMulticastConnection *connection = [signal publish];
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    [connection connect];複製程式碼

同樣publish方法也需要手動的呼叫connect方法。

3. replay


- (RACSignal *)replay {
    RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}複製程式碼

replay方法會把RACReplaySubject當成RACMulticastConnection的RACSubject傳遞進去,初始化好了RACMulticastConnection,再自動呼叫connect方法,返回的訊號就是轉換好的熱訊號,即RACMulticastConnection裡面的RACSubject訊號。

這裡必須是RACReplaySubject,因為在replay方法裡面先connect了。如果用RACSubject,那訊號在connect之後就會通過RACSubject把原訊號傳送給各個訂閱者了。用RACReplaySubject把訊號儲存起來,即使replay方法裡面先connect,訂閱者後訂閱也是可以拿到之前的訊號值的。

4. replayLast


- (RACSignal *)replayLast {
    RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];

    RACMulticastConnection *connection = [self multicast:subject];
    [connection connect];

    return connection.signal;
}複製程式碼

replayLast 和 replay的實現基本一樣,唯一的不同就是傳入的RACReplaySubject的Capacity是1,意味著只能儲存最新的值。所以使用replayLast,訂閱之後就只能拿到原訊號最新的值。

5. replayLazily


- (RACSignal *)replayLazily {
    RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
    return [[RACSignal
        defer:^{
            [connection connect];
            return connection.signal;
        }]
        setNameWithFormat:@"[%@] -replayLazily", self.name];
}複製程式碼

replayLazily 的實現也和 replayLast、replay實現很相似。只不過把connect放到了defer的操作裡面去了。

defer操作的實現如下:



+ (RACSignal *)defer:(RACSignal * (^)(void))block {
    NSCParameterAssert(block != NULL);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [block() subscribe:subscriber];
    }] setNameWithFormat:@"+defer:"];
}複製程式碼

defer 單詞的字面意思是延遲的。也和這個函式實現的效果是一致的。只有當defer返回的新訊號被訂閱的時候,才會執行入參block( )閉包。訂閱者會訂閱這個block( )閉包的返回值RACSignal。

block( )閉包被延遲建立RACSignal了,這就是defer。如果block( )閉包含有和時間有關的操作,或者副作用,想要延遲執行,就可以用defer。

還有一個類似的操作,then


- (RACSignal *)then:(RACSignal * (^)(void))block {
    NSCParameterAssert(block != nil);

    return [[[self
        ignoreValues]
        concat:[RACSignal defer:block]]
        setNameWithFormat:@"[%@] -then:", self.name];
}複製程式碼

then的操作也是延遲,只不過它是把block( )閉包延遲到原訊號傳送complete之後。通過then訊號變化得到的新的訊號,在原訊號傳送值的期間的時間內,都不會傳送任何值,因為ignoreValues了,一旦原訊號sendComplete之後,就緊接著block( )閉包產生的訊號。

回到replayLazily操作上來,作用同樣是把冷訊號轉換成熱訊號,只不過sourceSignal是在返回的新訊號第一次被訂閱的時候才被訂閱。原因就是defer延遲了block( )閉包的執行了。

最後

關於RACSignal的變換操作還剩下高階訊號操作,下篇接著繼續分析。最後請大家多多指教。

相關文章