ReactiveCocoa 常见类
信号源相关
RACStream
An abstract class representing any stream of values.
RACStream
是一个抽象类,是以 Monad
(函数式编程语言)的概念为依据进行设计的,它代表的就是一个 Monad
。有了 Monad
作为基石后,许多基于流的操作就可以被建立起来了,比如map
、 filter
、 zip
等。
RACSignal
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。
RACSignal
代表的是将来会被传递的值,它是一种 push-driven
的流。 RACSignal
可以向订阅者发送三种不同类型的事件:
-
next
:RACSignal
通过next
事件向订阅者传送新的值,并且这个值可以为nil
; -
error
:RACSignal
通过error
事件向订阅者表明信号在正常结束前发生了错误; -
completed
:RACSignal
通过completed
事件向订阅者表明信号已经正常结束,不会再有后续的值传送给订阅者。
注意, ReactiveCocoa
中的值流只包含正常的值,即通过 next
事件传送的值,并不包括 error
和 completed
事件,它们需要被特殊处理。通常情况下,一个信号的生命周期是由任意个next
事件和一个 error
事件或一个 completed
事件组成的。
RACSignal
的 Subscription
过程概括起来可以分为三个步骤:
- [RACSignal createSignal] 来获得 signal
- [signal subscribeNext:] 来获得 subscriber,然后进行subscription
- 进入 didSubscribe ,通过 [subscriber sendNext:] 来执行 next block 。
RACSubject
RACSubject
代表的是可以手动控制的信号,我们可以把它看作是 RACSignal
的可变版本,就好比 NSMutableArray
是 NSArray
的可变版本一样。 RACSubject
继承自 RACSignal
,所以它可以作为信号源被订阅者订阅,同时,它又实现了 RACSubscriber
协议,所以它也可以作为订阅者订阅其他信号源,这个就是 RACSubject
为什么可以手动控制的原因。
实际使用中,在 MVVM
中使用 RACSubject
可以非常方便地实现统一的错误处理逻辑。比如,我们可以在 viewModel
的基类中声明一个 RACSubject
类型的属性 errors
,然后在 viewController
的基类中编写统一的错误处理逻辑:
[self.viewModel.errors subscribeNext:^(NSError *error) {
// 错误处理逻辑
}
RACCommand
A command is a signal triggered in response to some action, typically UI-related.
RACCommand
通常用来表示某个 Action
的执行,比如点击 Button
。它有几个比较重要的属性:executionSignals / errors / executing。
-
executionSignals
:是 signal of signals ,如果直接 subscribe 的话会得到一个 signal ,而不是我们想要的 value,所以一般会配合switchToLatest
。 -
errors
:跟正常的 signal 不一样,RACCommand 的错误不是通过 sendError 来实现的,而是通过 errors 属性传递出来的。 -
executing
:表示该 command 当前是否正在执行。
RACSequence
Represents an immutable sequence of values. Unless otherwise specified, the sequences’ values are evaluated lazily on demand. Like Cocoa collections, sequences cannot contain nil.
RACSequence
代表的是一个不可变的值的序列,与 RACSignal
不同,它是 pull-driven
类型的流。从严格意义上讲, RACSequence
并不能算作是信号源,因为它并不能像 RACSignal
那样,可以被订阅者订阅,但是它与 RACSignal
之间可以非常方便地进行转换。
因此,我们可以非常方便地使用 RACSequence
来实现集合的链式操作,直到得到你想要的最终结果为止,常用的使用场景为「字典转模型」。
注意: RACSequence
会涉及到性能与效率的问题。
订阅者相关
RACSubscriber
Represents any object which can directly receive values from a RACSignal.
订阅者对信号源的一次订阅过程可以抽象为:通过 RACSignal
的 -subscribe:
方法传入一个订阅者,并最终返回一个 RACDisposable
对象的过程。
注意:在 ReactiveCocoa
中并没有专门的类 RACSubscription
来代表一次订阅,而间接地使用 RACDisposable
来充当这一角色。因此,一个 RACDisposable
对象就代表着一次订阅,并且我们可以用它来取消这次订阅。
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
trueNSCParameterAssert(nextBlock != NULL);
true
trueRACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
truereturn [self subscribe:o];
}
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
trueRACSubscriber *subscriber = [[self alloc] init];
truesubscriber->_next = [next copy];
truesubscriber->_error = [error copy];
truesubscriber->_completed = [completed copy];
truereturn subscriber;
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
trueNSCAssert(NO, @"This method must be overridden by subclasses");
truereturn nil;
}
调度器相关
RACScheduler
Schedulers are used to control when and where work is performed.
RACScheduler
在 ReactiveCocoa
中就是扮演着调度器的角色,本质上,它就是用 GCD
的串行队列来实现的,并且支持取消操作。是的,在 ReactiveCocoa
中,并没有使用到 NSOperationQueue
和 NSRunloop
等技术, RACScheduler
也只是对 GCD
的简单封装而已。
清洁工相关
RACDisposable
A disposable encapsulates the work necessary to tear down and cleanup a subscription.
RACDisposable
在 ReactiveCocoa
中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose
,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject
的 -dealloc
方法。
ReactiveCocoa 常见用法
代替代理
-
rac_signalForSelector
用户代替代理
代替 KVO
-
rac_valuesAndChangesForKeyPath
用于监听某个对象的某个属性的改变
代替事件监听
-
rac_signalForControlEvents
用于监听某个事件
代替通知
-
rac_addObserverForName
用于监听某个通知,且不需要在- (void)dealloc
中移除监听
监听文本框文字改变
-
rac_textSignal
用于监听文本框文字变化
代替手势
-
rac_gestureSignal
用于监听手势操作
多个请求完成时,再执行后继操作
-
rac_liftSelector:withSignalsFromArray:Signals
当传入的 Signals,每一个 Signal 都至少 sendNext 过一次,就会去触发第一个 selector 参数的方法。
信号的相关操作
-
bind
:函数会返回一个新的信号 N。整体思路是对原信号 O 进行订阅,每当信号 O 产生一个值就将其转变成一个中间信号 M ,并马上订阅 M ,之后将信号M的输出作为新信号 N 的输出。 -
map
\flattenMap
:用于把源信号内容映射成新的内容(信号)。 -
concat
:组合,按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。 -
then
:用于连接两个信号,当第一个信号完成,才会连接then
返回的信号。 -
merge
:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。 -
zipWith
:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next
事件。 -
combineLatest
:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal
至少都有过一次sendNext
,才会触发合并的信号。 -
reduce
:聚合,用于信号发出的内容是元组,把信号发出元组的值聚合成一个值。 -
filter
:过滤信号,使用它可以获取满足条件的信号。 -
ignore
:忽略某些值的信号,使用RACObserve
时可配合使用,其实现由filter
完成。 -
distinctUntilChanged
:实现是用bind
来完成的,每次变换中都记录一下原信号上一次发送过来的值,并与这一次进行比较,如果是相同的值,就「吞」掉,返回 empty 信号。只有和原信号上一次发送的值不同,变换后的新信号才把这个值发送出来。 -
take
:从开始一共取 N 次的信号。 -
takeLast
:取最后 N 次的信号,前提条件:订阅者必须调用完成,因为只有完成,才知道总共有多少信号。 -
takeUntil
:获取信号直到某个信号执行完成。 -
skip
:跳过几个信号,不接受。 -
switchToLatest
:用于signalOfSignals
(信号的信号),有时候信号也会发出信号,会在signalOfSignals
中,获取signalOfSignals
发送的最新信号。 -
doNext
:执行next
之前,会先执行这个 Block 。 -
doCompleted
:执行sendCompleted
之前,会先执行这个Block 。 -
timeout
:超时,可以让一个信号在一定的时间后,自动报错。 -
interval
:定时:每隔一段时间发出信号。 -
delay
:延迟发送next
。 -
retry
:重试,只要失败,就会重新执行创建信号中的 block ,直到成功。 -
replay
:重放,当一个信号被多次订阅,反复播放内容。 -
throttle
:节流,当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
ReactiveCocoa 常见宏
-
RAC(TARGET, ...)
用于绑定某个对象的某个属性 -
RACObserve(TARGET, KEYPATH)
用于监听某个对象的某个属性,返回的是信号 -
@weakify(Obj)
&@strongify(Obj)
配套使用
注意事项
Side Effect
Side effects occur for each subscription by default, but there are certain situations where side effects should only occur once – for example, a network request typically should not be repeated when a new subscriber is added.
如果某个信号被多个 subscriber
订阅,那么它的 didSubscribe
会被多次调用。
如果想要避免这种情况的发生,可以使用 reply
/ replayLast
/ replayLazily
方法,它们的作用是保证 signal
只被触发一次,然后把 sendNext:
的 value
给缓存起来,下一次再有新的 subscriber
时,直接发送缓存的 value
。
其内部实现依赖: - (RACMulticastConnection *)multicast:(RACSubject *)subject;
这个方法。
Cell 重用
RAC 给 UITableViewCell
提供了一个方法: rac_prepareForReuseSignal
,它的作用是当 Cell 即将要被重用时,告诉 Cell 。想象 Cell 上有多个 Button
,Cell 在初始化时给每个 Button 都 addTarget:action:forControlEvents
,被重用时需要先移除这些 target ,下面这段代码就可以很方便地解决这个问题:
[[[self.cancelButton
truerac_signalForControlEvents:UIControlEventTouchUpInside]
truetakeUntil:self.rac_prepareForReuseSignal]
truesubscribeNext:^(UIButton *x) {
true// do other things
}];
Strong / Weak Dance
因为 RAC 很多操作都是在 Block
中进行的,所以最常见的问题便是「循环引用」,所以需要通过 @weakify
和 @strongify
来消除循环引用。
注意:事实上 RACObserve(TARGET, KEYPATH)
总是会引用 self
,即使 target 不是 self ,所以只要有 RACObserve 的地方都要使用 @weakify
/ @strongify
。
flattenMap 与 map 的区别
-
flattenMap
中的 block 返回信号。 -
map
中的 block 返回对象。 -
map
的实现是用了flattenMap
函数来实现的。把map
的入参闭包,放到了flattenMap
的返回值中。 - 开发中,如果信号发出的值不是信号,映射一般使用
map
。 - 开发中,如果信号发出的值是信号,映射一般使用
flattenMap
。 -
signalOfsignals
用flattenMap
。
冷信号与热信号的区别
- Hot Observable 是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而 Cold Observable 是被动的,只有当你订阅的时候,它才会发布消息。
- Hot Observable 可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而 Cold Observable 只能一对一,当有不同的订阅者,消息是重新完整发送。
-
Subject
类似「直播」,错过了就不再处理。而Signal
类似「点播」。 -
RACSubject
及其子类是热信号,RACSignal
排除RACSubject
类以外的是冷信号。 -
RACSubject
会持有订阅者(因为RACSubject
是热信号,为了保证未来有事件发送的时候,订阅者可以收到信息,所以需要对订阅者保持状态,做法就是持有订阅者),而RACSignal
不会持有订阅者。
其他
当一个
signal
被一个subscriber
subscribe 后,这个subscriber
何时会被移除?答案是:当subscriber
被sendComplete
或sendError
时,或者手动调用[disposable dispose]
。replay
是multicast
的一个特殊 case 而已。当
subscriber
被dispose
后,所有该subscriber
相关的工作都会被停止或取消,如 http 请求,资源也会被释放。Errors
有优先权,如果有多个signals
被同时监听,只要其中一个 signalsendError
,那么 error 就会立刻被传送给 subscriber ,并导致 signals 终止执行。相当于Exception
。使用
RACSubject
,如果进行了map
操作,那么一定要发送完成信号,不然会内存泄漏。任何的信号转换即是对原有的信号进行订阅从而产生新的信号。