Halfrost-Field 项目解析:ReactiveCocoa 中冷热信号的底层实现
前言
在响应式编程框架 ReactiveCocoa 中,信号(Signal)是最核心的概念之一。信号可以分为冷信号(Cold Signal)和热信号(Hot Signal),理解它们的区别和实现原理对于正确使用 ReactiveCocoa 至关重要。本文将深入分析 ReactiveCocoa 中冷热信号的底层实现机制。
冷信号与热信号的概念
基本定义
冷信号和热信号的概念源自 .NET 框架中的 Reactive Extensions (RX):
- 热信号:主动推送数据,即使没有订阅者也会持续发送事件(如鼠标移动事件)
- 冷信号:被动响应,只有在被订阅时才会开始发送数据
核心区别
-
订阅时机:
- 热信号:无论何时订阅,都能接收到订阅后的数据
- 冷信号:每次订阅都会从头开始接收完整的数据序列
-
订阅者关系:
- 热信号:一对多,多个订阅者共享同一数据流
- 冷信号:一对一,每个订阅者都会获得独立的数据流
ReactiveCocoa 中的热信号实现
1. RACSubject
RACSubject 是最基础的热信号实现,它同时继承自 RACSignal 并实现了 RACSubscriber 协议,因此既能发送信号也能接收信号。
关键特性:
- 内部维护一个订阅者数组(subscribers)
- 发送事件时会遍历所有订阅者并逐一通知
- 订阅时会将订阅者加入数组,取消订阅时移除
// 订阅实现
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
[self.subscribers addObject:subscriber];
// ...
}
// 发送事件实现
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
2. RACBehaviorSubject
RACBehaviorSubject 是 RACSubject 的变种,它会保存并自动发送最近一次的值给新订阅者。
关键特性:
- 初始化时可设置默认值
- 新订阅者会立即收到当前值
- 每次发送新值都会更新存储的值
// 订阅实现
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
[super subscribe:subscriber];
[subscriber sendNext:self.currentValue]; // 立即发送当前值
}
3. RACReplaySubject
RACReplaySubject 会缓存所有发送过的值,并在新订阅者订阅时重放这些值。
关键特性:
- 可设置缓存容量(默认无限)
- 新订阅者会收到所有历史值
- 超过容量时会自动移除最早的值
// 发送事件实现
- (void)sendNext:(id)value {
[self.valuesReceived addObject:value]; // 缓存值
[super sendNext:value];
// 容量控制
if (self.capacity != RACReplaySubjectUnlimitedCapacity &&
self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:...];
}
}
ReactiveCocoa 中的冷信号实现
1. RACDynamicSignal
RACDynamicSignal 是 createSignal: 方法创建的实际信号类型,是典型的冷信号。
关键特性:
- 每次订阅都会执行 didSubscribe 闭包
- 为每个订阅者创建独立的执行上下文
- 适合封装一次性操作(如网络请求)
2. RACEmptySignal/RACReturnSignal
这些是特殊的冷信号实现:
- RACEmptySignal:立即发送 completed 事件
- RACReturnSignal:立即发送一个值然后 completed
// RACReturnSignal 订阅实现
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
[subscriber sendNext:self.value];
[subscriber sendCompleted];
}
3. RACChannelTerminal
用于双向绑定的特殊信号,虽然具备发送和接收能力,但仍是冷信号。
关键特性:
- 内部使用 RACReplaySubject 实现
- 主要用于 View 和 ViewModel 的双向绑定
- 订阅时实际订阅的是内部的 RACReplaySubject
冷信号转热信号的实现
为什么需要转换
冷信号每次订阅都会重新执行操作(如网络请求),这在以下场景不适用:
- 多个订阅者共享同一数据源
- 避免重复执行昂贵操作
- 需要缓存历史数据
RACMulticastConnection
ReactiveCocoa 提供了 RACMulticastConnection 来实现冷信号到热信号的转换。
工作原理:
- 内部使用 RACSubject 作为桥梁
- 冷信号只被 RACSubject 订阅一次
- 多个外部订阅者实际订阅的是 RACSubject
// 连接建立过程
- (RACDisposable *)connect {
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
// 使用示例
RACSignal *coldSignal = [RACSignal createSignal:...];
RACMulticastConnection *connection = [coldSignal multicast:[RACSubject subject]];
[connection connect];
// 多个订阅者订阅的是热信号
[connection.signal subscribe:...];
[connection.signal subscribe:...];
自动连接
autoconnect 方法可以创建在第一个订阅者出现时自动连接的信号:
- (RACSignal *)autoconnect {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
RACDisposable *connectionDisposable = [self connect];
return [RACDisposable disposableWithBlock:^{
[subscriptionDisposable dispose];
[connectionDisposable dispose];
}];
}] setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}
总结
理解冷热信号的差异和实现机制对于高效使用 ReactiveCocoa 至关重要:
- 冷信号:适合一次性操作,每次订阅独立执行
- 热信号:适合共享数据流,多个订阅者共享同一上下文
- 转换机制:通过 RACMulticastConnection 实现冷到热的转换
正确选择和使用信号类型可以避免常见问题(如重复网络请求),并构建更高效的响应式应用。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考