RAC信号操作解释合集

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容