前言
關於ReactiveCocoa v2.5中冷訊號和熱訊號的文章中,最著名的就是美團的臧成威老師寫的3篇冷熱訊號的文章:
細說ReactiveCocoa的冷訊號與熱訊號(一)
細說ReactiveCocoa的冷訊號與熱訊號(二):為什麼要區分冷熱訊號
細說ReactiveCocoa的冷訊號與熱訊號(三):怎麼處理冷訊號與熱訊號
由於最近在寫關於RACSignal底層實現分析的文章,當然也逃不了關於冷熱訊號操作的分析。這篇文章打算分析分析如何從冷訊號轉成熱訊號的底層實現。
目錄
- 1.關於冷訊號和熱訊號的概念
- 2.RACSignal熱訊號
- 3.RACSignal冷訊號
- 4.冷訊號是如何轉換成熱訊號的
一. 關於冷訊號和熱訊號的概念
冷熱訊號的概念是源自於源於.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,
Hot Observable是主動的,儘管你並沒有訂閱事件,但是它會時刻推送,就像滑鼠移動;而Cold Observable是被動的,只有當你訂閱的時候,它才會釋出訊息。
Hot Observable可以有多個訂閱者,是一對多,集合可以與訂閱者共享資訊;而Cold Observable只能一對一,當有不同的訂閱者,訊息是重新完整傳送。
在這篇文章細說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 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訂閱和傳送訊號的流程,我們可以知道,每訂閱一次冷訊號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的訊號值依次發給它的訂閱者們。
用一個不恰當的比喻來形容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的變換操作還剩下高階訊號操作,下篇接著繼續分析。最後請大家多多指教。