1. 程式人生 > >ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(下)

ReactiveCocoa 中 RACSignal 所有變換操作底層實現分析(下)

111194012-b855c246fe97dd2f

前言

緊接著上篇的原始碼實現分析,繼續分析RACSignal的變換操作的底層實現。

目錄

  • 1.高階訊號操作
  • 2.同步操作
  • 3.副作用操作
  • 4.多執行緒操作
  • 5.其他操作

一. 高階訊號操作

121194012-36df639e95dca37c

高階操作大部分的操作是針對高階訊號的,也就是說訊號裡面傳送的值還是一個訊號或者是一個高階訊號。可以類比陣列,這裡就是多維陣列,數組裡面還是套的陣列。

1. flattenMap: (在父類RACStream中定義的)

flattenMap:在整個RAC中具有很重要的地位,很多訊號變換都是可以用flattenMap:來實現的。

map:,flatten,filter,sequenceMany:這4個操作都是用flattenMap:來實現的。然而其他變換操作實現裡面用到map:,flatten,filter又有很多。

回顧一下map:的實現:

12345678 -(instancetype)map:(id(^)(id value))block{NSCParameterAssert(block!=nil);Classclass=self.class;return[[selfflattenMap:^(id value){return[classreturn:block(value)];}]setNameWithFormat:@"[%@] -map:",self.name];}

map:的操作其實就是直接原訊號進行的 flattenMap:的操作,變換出來的新的訊號的值是block(value)。

flatten的實現接下去會具體分析,這裡先略過。

filter的實現:

12345678 -(instancetype)filter:(BOOL(^)(id value))block{NSCParameterAssert(block!=nil);Classclass=self.class;return[[selfflattenMap:^id(id value){block(value)?return[classreturn:value]:returnclass.empty;}]setNameWithFormat:@"[%@] -filter:",self.name];}

filter的實現和map:有點類似,也是對原訊號進行 flattenMap:的操作,只不過block(value)不是作為返回值,而是作為判斷條件,滿足這個閉包的條件,變換出來的新的訊號返回值就是value,不滿足的就返回empty訊號

接下去要分析的高階操作裡面,switchToLatest,try:,tryMap:的實現中也將會使用到flattenMap:。

flattenMap:的原始碼實現:

123456789101112 -(instancetype)flattenMap:(RACStream *(^)(id value))block{Classclass=self.class;return[[selfbind:^{return^(id value,BOOL*stop){id stream=block(value)?:[classempty];NSCAssert([stream isKindOfClass:RACStream.class],@"Value returned from -flattenMap: is not a stream: %@",stream);returnstream;};}]setNameWithFormat:@"[%@] -flattenMap:",self.name];}

flattenMap:的實現是呼叫了bind函式,對原訊號進行變換,並返回block(value)的新訊號。關於bind操作的具體流程這篇文章裡面已經分析過了,這裡不再贅述。

從flattenMap:的原始碼可以看到,它是可以支援類似Promise的序列非同步操作的,並且flattenMap:是滿足Monad中bind部分定義的。flattenMap:沒法去實現takeUntil:和take:的操作。

然而,bind操作可以實現take:的操作,bind是完全滿足Monad中bind部分定義的。

2. flatten (在父類RACStream中定義的)

flatten的原始碼實現:

123456 -(instancetype)flatten{__weak RACStream *stream __attribute__((unused))=self;return[[selfflattenMap:^(id value){returnvalue;}]setNameWithFormat:@"[%@] -flatten",self.name];}

flatten操作必須是對高階訊號進行操作,如果訊號裡面不是訊號,即不是高階訊號,那麼就會崩潰。崩潰資訊如下:

1 ***Terminating app due touncaught exception'NSInternalInconsistencyException',reason:'Value returned from-flattenMap:isnotastream

所以flatten是對高階訊號進行的降階操作。高階訊號每傳送一次訊號,經過flatten變換,由於flattenMap:操作之後,返回的新的訊號的每個值就是原訊號中每個訊號的值。

131194012-4c6a1f8d3f377beb

如果對訊號A,訊號B,訊號C進行merge:操作,可以達到和flatten一樣的效果。

1 [RACSignal merge:@[signalA,signalB,signalC]];

merge:操作在上篇文章分析過,再來複習一下:

123456789101112131415161718 +(RACSignal *)merge:(id)signals{NSMutableArray *copiedSignals=[[NSMutableArray alloc]init];for(RACSignal *signal insignals){[copiedSignals addObject:signal];}return[[[RACSignalcreateSignal:^RACDisposable *(id subscriber){for(RACSignal *signal incopiedSignals){[subscriber sendNext:signal];}[subscriber sendCompleted];returnnil;}]flatten]setNameWithFormat:@"+merge: %@",copiedSignals];}

現在在回來看這段程式碼,copiedSignals雖然是一個NSMutableArray,但是它近似合成了一個上圖中的高階訊號。然後這些訊號們每傳送出來一個訊號就發給訂閱者。整個操作如flatten的字面意思一樣,壓平。

141194012-f1e53607f0f315bd

另外,在ReactiveCocoa v2.5中,flatten預設就是flattenMap:這一種操作。

123456789101112 publicfunc flatten(_strategy:FlattenStrategy)->Signal{switchstrategy{case.merge:returnself.merge()case.concat:returnself.concat()case.latest:returnself.switchToLatest()}}

而在ReactiveCocoa v3.x,v4.x,v5.x中,flatten的操作是可以選擇3種操作選擇的。merge,concat,switchToLatest。

3. flatten:

flatten:操作也必須是對高階訊號進行操作,如果訊號裡面不是訊號,即不是高階訊號,那麼就會崩潰。

flatten:的實現比較複雜,一步步的來分析:

1234567891011121314151617181920212223242526272829303132333435363738 -(RACSignal *)flatten:(NSUInteger)maxConcurrent{return[[RACSignal createSignal:^(id subscriber){RACCompoundDisposable *compoundDisposable=[[RACCompoundDisposable alloc]init];NSMutableArray *activeDisposables=[[NSMutableArray alloc]initWithCapacity:maxConcurrent];NSMutableArray *queuedSignals=[NSMutableArray array];__block BOOLselfCompleted=NO;__block void(^subscribeToSignal)(RACSignal *);__weak __block void(^recur)(RACSignal *);recur=subscribeToSignal=^(RACSignal *signal){// 暫時省略};void(^completeIfAllowed)(void)=^{// 暫時省略};[compoundDisposable addDisposable:[selfsubscribeNext:^(RACSignal *signal){if(signal==nil)return;NSCAssert([signal isKindOfClass:RACSignal.class],@"Expected a RACSignal, got %@",signal);@synchronized(subscriber){if(maxConcurrent>0&&activeDisposables.count>=maxConcurrent){[queuedSignals addObject:signal];return;}}subscribeToSignal(signal);}error:^(NSError *error){[subscriber sendError:error];}completed:^{@synchronized(subscriber){selfCompleted=YES;completeIfAllowed();}}]];returncompoundDisposable;}]setNameWithFormat:@"[%@] -flatten: %lu",self.name,(unsignedlong)maxConcurrent];}

先來解釋一些變數,陣列的作用

activeDisposables裡面裝的是當前正在訂閱的訂閱者們的disposables訊號。

queuedSignals裡面裝的是被暫時快取起來的訊號,它們等待被訂閱。

selfCompleted表示高階訊號是否Completed。

subscribeToSignal閉包的作用是訂閱所給的訊號。這個閉包的入參引數就是一個訊號,在閉包內部訂閱這個訊號,並進行一些操作。

recur是對subscribeToSignal閉包的一個弱引用,防止strong-weak迴圈引用,在下面會分析subscribeToSignal閉包,就會明白為什麼recur要用weak修飾了。

completeIfAllowed的作用是在所有訊號都發送完畢的時候,通知訂閱者,給訂閱者傳送completed。

入參maxConcurrent的意思是最大可容納同時被訂閱的訊號個數。

再來詳細分析一下具體訂閱的過程。

flatten:的內部,訂閱高階訊號發出來的訊號,這部分的程式碼比較簡單:

1234567891011121314151617181920212223 [selfsubscribeNext:^(RACSignal *signal){if(signal==nil)return;NSCAssert([signal isKindOfClass:RACSignal.class],@"Expected a RACSignal, got %@",signal);@synchronized(subscriber){// 1if(maxConcurrent>0&&activeDisposables.