我一直觉得,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对象,第二个是参数,然后执行。。。