我一直覺得,RAC是仿佛已經被遺忘的話題,擅長的人已經把它化為內力,不擅長的早已忘記這個技術的存在,這個暫且按住不談。我們都知道RAC的強大在于它豐富的信號操作,RAC提供了幾十種信號操作,掌握這些信號操作,無疑會大大增強使用RAC的效力。這篇文章其實15年就寫了,一直放在印象筆記里面,自己查閱使用,現在有共享出去的需求,所以拿出來放在這里。
這些內容,都是當時研究RAC的時候,閱讀源碼的時候做的筆記,有許多當時理解錯誤和不足的地方,如果有誤導,非常抱歉;加上當時時間比較倉促,還有其他的任務,很多內容沒有經過校正和確定,所以肯定會有謬誤的地方。僅作參考,如果發現錯誤,歡迎留言,我這邊會予以更改。我也會抽時間進行再次研究和修正。RAC的門檻很高,初學者有諸多障礙,所以業內的關注度有限。不關注不代表不重要,掌握響應式編程的用法,打破自己一貫的編程方式,對于嚴格要求自己的開發者的重要性不言而喻。同時對于工程的實踐意義重大,越復雜的項目,使用RAC越有勢如破竹之感。希望有一天,這個可以成為一個很大眾,被多數人掌握的技術。
scanWithStart
- (instancetype)scanWithStart:(id)startingValue reduceWithIndex:(id(^)(id,id,NSUInteger))reduceBlock
///使用reduceBlock從左到右合并receiver包含的值.這是一個很有趣的操作,暫時沒發現有什么用處。詳細解釋一下:
startingValue是第一個left值,信號的第一個值是right值,使用block進行合并,返回新值,以后的left是上一次合并之后的值,right是next的值。效果的疊加。可以參見文檔對數組的操作示例
scanWithStart
- (instancetype)scanWithStart:(id)startingValue reduce:(id(^)(idrunning,idnext))reduceBlock;
///除了少一個index的參數,其余同上
combinePreviousWithStart
- (instancetype)combinePreviousWithStart:(id)start reduce:(id(^)(idprevious,idnext))reduceBlock
///合并left和right的值,但是不會把合并的結果和下一個值在疊加效果,即每次都是left和right的疊加。實現原理是scanWithStart和map的組合。
ignore
- (instancetype)ignore:(id)value
///忽略信號中的某些值
reduceEach
- (instancetype)reduceEach:(id(^)())reduceBlock
///信號必須發送的是一組值(RACTuple),通過reduceBlock接收一組值,然后處理返回一個值
startWith
- (instancetype)startWith:(id)value
///信號會首先發送一個值,實現原理是concat
skip
- (instancetype)skip:(NSUInteger)skipCount
///跳過前幾次的值,實現原理是bind
take
- (instancetype)take:(NSUInteger)count
///取前幾次的值,實現原理是bind
join
+ (instancetype)join:(id)streams block:(RACStream* (^)(id,id))block
///類方法,私有方法。合并一群信號,方式是left、right形式的,即一一合并,第一個和第二個合并,合并的結果在和第三個合并,而合并是block執行,輸入的參數是left、right,返回一個合并好的stream,直到合并結束,返回一個stream。然后在把這個stream的值拆包(RACTuple)。最終的作用是把幾個stream的值合并到一個數組中輸出。但是是內部方法,不對外提供調用接口,是其他幾個操作的基礎。關鍵在于block的實現。
zip
+ (instancetype)zip:(id)streams
///類方法。它合并一組信號。必須任何一個信號都發送了一次值,合并后的信號才會把這幾個信號的同一批次的值封裝為RACTuple發送出去。例如合并兩個信號,第一個信號發了兩次1,2,第二個沒發送,那么不會有信號發出;如果第二個信號發送一次3,那么合并之后的信號會發送一個RACTuple(1,3),除非第二個信號在發送第二個值,第一個信號發送的第二個值才會被一起包裝作為一次信號發送出去。實現原理是上面的join加上zipWith。
zip
+ (instancetype)zip:(id)streams reduce:(id(^)())reduceBlock
///類方法。根據上面一個方法的解釋。它添加一個功能是,把所有的組合值,經過reduceBlock處理,合并成一個值返回。實現原理是zip加上reduceEach。
concat
+ (instancetype)concat:(id)streams
///類方法。把一組信號串聯起來,前面一個信號complete,后面一個信號才開始發揮作用。
takeUntilBlock
- (instancetype)takeUntilBlock:(BOOL(^)(idx))predicate
///predicate返回yes的時候,停止信號的訂閱。實現原理是bind。
takeWhileBlock
- (instancetype)takeWhileBlock:(BOOL(^)(idx))predicate
///和上面一個方法相反。
skipUntilBlock
skipWhileBlock
distinctUntilChanged
///這兩個方法和上面兩個方法相反,不一一解釋。
doNext
- (RACSignal*)doNext:(void(^)(idx))block;
///用來往信號注入side effects,顯示標記side effects的方法,在每次信號subscribeNext之前執行。詳見https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md#transforming-streams
有示例解釋。實現很簡單,訂閱原始信號,返回新的信號,原始信號有數據流動的時候,執行doNext的block,同時新的信號被訂閱,直接向訂閱者發送sendNext消息和數據參數。side effects的
doError
///用來消除side effects的方法,解釋同上原理。
doCompleted
///用來消除side effects的方法,解釋同上原理。
throttle
- (RACSignal*)throttle:(NSTimeInterval)interval;
///每次發送的數據,都經過interval的間隔之后才發出。在interval時間內發送的所有信號只有最后一個數據被發送,前面的都會被拋棄。
throttle
- (RACSignal*)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL(^)(idnext))predicate;
///每次發送的數據,經過predicate block的過濾,返回NO,代表不需要被過濾,直接發送數據,返回YES,那么需要經過interval的間隔之后才發出。在interval時間內發送的所有信號只有最后一個數據被發送,前面的都會被拋棄。
delay
- (RACSignal*)delay:(NSTimeInterval)interval
///每次發送的next和complete事件,都有interval時間的延遲,但是error一直都會立即發送。
repeat
- (RACSignal*)repeat;
///當信號發送complete的時候,重新訂閱。
initially
- (RACSignal*)initially:(void(^)(void))block;
/// 當每次訂閱產生的時候,會調用initially方法。這個方法的參數block是定義:訂閱side effect的地方。就像doNext一樣,在每次sendNext之前會被調用,用來標記sendNext的side effect效應。這一類的方法統稱為顯式的表達side effect(見side effect分析)
finally
- (RACSignal*)finally:(void(^)(void))block;
/// 當發送completes or errors事件的時候被調用,解釋同上(side effect)
bufferWithTime
- (RACSignal*)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler*)scheduler;
///把信號的數據緩存起來,每interval秒發送一次,當發送complete的時候,會把當前的buffer內容全部發出去。實現的原理是,內部訂閱原始信號,每次原始信號的值被發送的時候,有一個可變數組把它存起來,并且dispatch_after interval秒之后,執行新的信號的sendNext(發送的是RACTuple對象,包含緩存的數據)
collect
- (RACSignal*)collect;
///這個方法使用和理解起來比較簡單,他把信號所有的數據都存到一個數組里面,等到complete事件的時候,把這個數組發出去。實現原理是:它的實現原理比較復雜,是一連串操作的集合。經過aggregateWithStartFactory操作、defer操作、scanWithStart、bind等等(后續解釋)。總之,它的大致原理是:1、scanWithStart,前面我們解釋過,這個是使用block合并信號的前一個值和下一個值,返回一個新值的操作,初始值我們指定一個可變數組,block的操作是把下一個值添加到這個數組中來;2、takeLast操作來保證原始信號結束之后(complete)發送最近的一個結果。
takeLast
- (RACSignal*)takeLast:(NSUInteger)count;
///當原始信號發送complete之后,發送count數量的最近的next數據。實現原理是:內部訂閱原始信號,新建一個count個數的數組,每一個值都被存儲起來,超出數組數量的時候,移除最初的數據,保證數組數量最多只有count個,當complete的時候,send出去這些數據。
combineLatestWith
- (RACSignal*)combineLatestWith:(RACSignal*)signal;
///每當2個初始的信號發送next事件的時候,返回的信號都會把兩個信號最近的值發送出去。實現原理是:內部訂閱兩個信號,剩下的你懂的。(必須2個信號都最少返回過數據)
combineLatest
+ (RACSignal*)combineLatest:(id)signals;
///合并一批信號,剩余的同上,實現原理是:基于join和combineLatestWith,join前面解釋過,合并一批stream,采用left、right的方式,而left每次是block提供,這里block的實現是基于combineLatestWith的原理合并,join在負責最后的拆包(拆一層層的RACTuple,詳見前文介紹)
combineLatest
+ (RACSignal*)combineLatest:(id)signals reduce:(id(^)())reduceBlock;
///合并一批信號,返回的值會作為參數的形式通過reduceBlock執行,返回一個值,官方有例子介紹。實現原理是:通過combineLastest和reduceEach(前問有解釋),把一組值通過參數block合并為一個值。
merge
- (RACSignal*)merge:(RACSignal*)signal;
///合并兩個信號,這個和combine的區別是:combine合并信號,每次發送的一個RACTuple對象,即包含每個信號的最新的value,而merge合并的信號的意思是,每個原始信號sendNext都會被當作返回信號的一次值發送(基于bind,綁定)。它的實現原理是flatten,首先創建一個信號,信號發送的數據是要被合并的信號(循環),然后flatten操作是攤平這些信號,走到flattenMap操作,它的參數是一個直接返回接收參數的block對象,我們知道flattenMap的內部實現是基于bind,bind內部訂閱原始信號-》信號經過剛才的block處理(直接返回)-》訂閱這個block執行之后返回的信號-》信號的每次發送數據都等于最終返回的信號在發送數據(subscribe sendNext:…)。flattenMap有修改的意味,但是flatten直接返回的是信號本身。
merge
+ (RACSignal*)merge:(id)signals;
///同上
flatten
- (RACSignal*)flatten:(NSUInteger)maxConcurrent;
///這是flatten的另一種實現,順帶有附加效果:最大訂閱數,超過最大訂閱數的信號被隊列管理起來,當訂閱的信號complete的時候,從隊列中補充一個進來。可以參考一下和flatten的實現區別,一個基于flattenMap,只不過是沒有map,另一個是基本的實現,就是原始信號發送的都是信號,直接訂閱這些信號,數據作為返回的信號數據,類似于簡化版的bind。
then
- (RACSignal*)then:(RACSignal* (^)(void))block;
///忽略所有的原始信號值,直到complete,然后訂閱后面的信號。實現原理是:concat(前文有解釋)
concat
- (RACSignal*)concat;
///前文有解釋,這里分析實現原理:它的核心操作就是一句話flatten: 1 ? 我們知道flatten是控制最大訂閱數,一個結束,另一個補充上來
aggregateWithStart
- (RACSignal*)aggregateWithStart:(id)start reduce:(id(^)(idrunning,idnext))reduceBlock;
///這系列的3個方法都見上面collect的方法解釋。
setKeyPath
- (RACDisposable*)setKeyPath:(NSString*)keyPath onObject:(NSObject*)object;
- (RACDisposable*)setKeyPath:(NSString*)keyPath onObject:(NSObject*)object nilValue:(id)nilValue
///把一個信號和一個對象的keyPath關聯起來,每次信號發送數據,會被自動綁定到對象的該屬性上面
interval
+ (RACSignal*)interval:(NSTimeInterval)interval onScheduler:(RACScheduler*)scheduler;
+ (RACSignal*)interval:(NSTimeInterval)interval onScheduler:(RACScheduler*)scheduler withLeeway:(NSTimeInterval)leeway;
///在scheduler上面部署每隔多少秒執行任務。實現原理是基于- (RACDisposable*)after:(NSDate*)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void(^)(void))block;底層原理是GCD技術。
takeUntil
- (RACSignal*)takeUntil:(RACSignal*)signalTrigger;
- (RACSignal*)takeUntilReplacement:(RACSignal*)replacement;
///原始信號一直發送信號,直到,替代的信號發出事件,原始信號終止。
catch
- (RACSignal*)catch:(RACSignal* (^)(NSError*error))catchBlock;
- (RACSignal*)catchTo:(RACSignal*)signal;
///原始信號出現錯誤的時候,使用block返回新的信號替代,block的參數是錯誤信息
try
- (RACSignal*)try:(BOOL(^)(idvalue,NSError**errorPtr))tryBlock;
- (RACSignal*)tryMap:(id(^)(idvalue,NSError**errorPtr))mapBlock;
///原始信號每次發送的信號都會經過tryBlock的檢查,如果返回NO,那么信號會發送error事件。map類似。都是基于flattenMap。
first
- (id)first;
- (id)firstOrDefault:(id)defaultValue;
- (id)firstOrDefault:(id)defaultValue success:(BOOL*)success error:(NSError**)error;
///返回第一個next數據,這個是一個阻塞調用(會阻塞當前線程),使用了NSCondition狀態鎖機制。
waitUntilCompleted
- (BOOL)waitUntilCompleted:(NSError**)error;
///阻塞直到完成(sendComplete)
defer
+ (RACSignal*)defer:(RACSignal* (^)(void))block;
///直到訂閱時候才真正創建一個信號
switchToLatest
- (RACSignal*)switchToLatest;
+ (RACSignal*)switch:(RACSignal*)signal cases:(NSDictionary*)casesdefault:(RACSignal*)defaultSignal;
///原始信號必須是發送信號的信號(sendNext:(RACSignal)…),發送的信號會被訂閱,直到發送下一個信號,前一個被發送的信號就終止訂閱,方法的作用是,每次訂閱最新的信號。和map一起往往被認為是替代flattenMap的方案。
if
+ (RACSignal*)if:(RACSignal*)boolSignal then:(RACSignal*)trueSignalelse:(RACSignal*)falseSignal;
///boolSignal返回bool值,決定當前訂閱trueSignal還是falseSignal。基于switchToLatest和Map
toArray
- (NSArray*)toArray;
///這是一個阻塞操作,complete的時候把所有的信號數據發出去(數組),基于collect和first操作
sequence
@property(nonatomic,strong,readonly)RACSequence*sequence;
///把信號轉換成RACSequence
publish
- (RACMulticastConnection*)publish;
- (RACMulticastConnection*)multicast:(RACSubject*)subject;
- (RACSignal*)replay;
- (RACSignal*)replayLast;
- (RACSignal*)replayLazily;
///多播系列,見side effects講解
timeout
- (RACSignal*)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler*)scheduler;
///設置超時處理,超時會發error事件
deliverOn
- (RACSignal*)deliverOn:(RACScheduler*)scheduler;
///在指定的scheduler分發三種事件,side effect還會在原始的scheduler上
subscribeOn
- (RACSignal*)subscribeOn:(RACScheduler*)scheduler;
///side effect和三種事件都在指定的scheduler上分發,這個操作比較危險,因為side effect可能不是線程安全的。
deliverOnMainThread
///在主線程上分發事件
groupBy
- (RACSignal*)groupBy:(id (^)(idobject))keyBlock transform:(id(^)(idobject))transformBlock;
- (RACSignal *)groupBy:(id (^)(idobject))keyBlock;
///把信號分組
any
- (RACSignal*)any;
- (RACSignal*)any:(BOOL(^)(idobject))predicateBlock;
///原始信號發送next,返回的信號會發送[NSNumber numberWithBool:YES],然后終止訂閱
all
- (RACSignal*)all:(BOOL(^)(idobject))predicateBlock;
///原始信號發送所有的next通過predicateBlock驗證,才會返回YES,否者為NO
retry
- (RACSignal*)retry;
- (RACSignal*)retry:(NSInteger)retryCount;
///發送error事件之后重試,重新訂閱
sample
- (RACSignal*)sample:(RACSignal*)sampler;
///sampler每次發送信號的時候,會把原始信號的最新值發出去
ignoreValues
///忽略所有的next值,只接收complete和error事件
materialize
///把每次信號的值封裝成RACEvent對象
dematerialize
///跟上面相反,解封
not
///原始信號必須發送NSNumber,not操作會返回轉化后的bool值的相反值
and
///原始信號必須發送RACTuple,包含的必須是NSNumber對象,所有的對象轉換后的bool值必須是YES,才會返回YES
or
///原始信號必須發送RACTuple,包含的必須是NSNumber對象,只要有對象轉換后的bool值是YES就會返回YES
reduceApply
///原始信號發送RACTuple,包含最少2個要素,第一個是block對象,第二個是參數,然后執行。。。