我们来深入剖析一下 RACSignal 的实现原理。
RACSignal 是 ReactiveCocoa 中最核心的类之一,它代表了一个异步数据流 。理解其原理对于掌握 ReactiveCocoa 至关重要。它的实现可以概括为:基于订阅的、惰性的、单向的、推送式的数据流。
其核心原理主要围绕以下几个关键点展开:
1. 核心思想:Pull vs. Push
- Pull (拉取): 传统的编程模式。消费者在需要值时主动去获取(例如,访问一个属性、调用一个函数)。控制权在消费者手中。
- Push (推送): 响应式编程模式。生产者在你不知道的时候"推送"值给你。你只需要订阅它,并在值到来时做出反应。控制权在生产者手中。
RACSignal 就是一个 Push 模式的实现。你无法主动要求一个 Signal 给你值,你只能订阅它,然后等待它将来某个时间点可能给你发送值。
2. 核心机制:订阅(Subscription)
这是 RACSignal 最精髓的部分。一个 Signal 在被订阅前,什么都不做(惰性求值)。只有当 subscribeNext:error:completed:
被调用时,它才开始执行它的工作(例如发起网络请求、监听通知等)。
订阅过程可以抽象为以下步骤:
- 创建信号 (Create) : 使用
[RACSignal createSignal:]
类方法创建一个信号。这个方法的参数是一个 block,我们称之为didSubscribe
block。 - 订阅信号 (Subscribe) : 调用者对这个信号执行
subscribeNext:...
方法。 - 触发执行 (Trigger) :
subscribeNext:...
方法内部会去执行第一步中创建的didSubscribe
block。 - 传递事件 (Send Events) : 在
didSubscribe
block 中,你可以通过提供的subscriber
对象发送next
,error
,completed
事件。 - 处理事件 (Handle Events) :
subscribeNext:...
方法中传入的 blocks 会相应地处理这些事件。 - 销毁清理 (Dispose) : 当信号发送
error
或completed
事件,或者订阅被手动取消时,会执行清理工作。
3. 内部实现剖析(简化版代码)
让我们通过简化版的代码来理解这个过程。
第一步:创建信号
objc
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
// RACDynamicSignal 是 RACSignal 的一个子类,负责具体实现
@interface RACDynamicSignal ()
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
@end
@implementation RACDynamicSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy]; // 关键:保存 didSubscribe block
return signal;
}
@end
创建信号时,只是简单地将 didSubscribe
这个 block 保存起来,此时 block 并不会执行。
第二步:订阅信号
RACSignal
的订阅方法最终会调用到以下核心方法:
objc
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
// 1. 创建一个内部的分发器(可能用于处理 `RACDisposable` 和线程安全)
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
// 2. 确保订阅者不为空
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
// 3. 最关键的一步:从第一步中取出保存的 didSubscribe block 并执行它!
// 同时,将返回的 RACDisposable 对象加入到复合 disposable 中用于管理生命周期
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 这里终于执行了创建信号时传入的 block!
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
// 4. 返回一个 Disposable,用于取消订阅
return disposable;
}
当你调用 subscribeNext:...
时,内部会调用这个 subscribe:
方法。这时,之前保存的 didSubscribe
block 被真正执行了。
第三步:在 didSubscribe Block 中发送事件
作为信号创建者,你在 createSignal:
的 block 里做的事情就是定义数据流:
objc
RACSignal *mySignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 这个 block 只在有人订阅时执行一次
NSLog(@"Starting network request...");
// 模拟异步网络请求
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:2.0]; // 模拟延迟
NSString *result = @"Data from server";
// 成功获取数据,发送 Next 事件和值
[subscriber sendNext:result];
// 请求完成,发送 Completed 事件
[subscriber sendCompleted];
});
// 返回一个 RACDisposable,用于在订阅被取消时清理资源(如取消网络请求)
return [RACDisposable disposableWithBlock:^{
NSLog(@"Subscription cancelled, cleanup here (e.g., cancel request).");
}];
}];
第四步:处理事件
订阅者(调用方)的处理:
objc
// 这一行代码触发了上面所有的流程
[mySignal subscribeNext:^(id x) {
NSLog(@"Received value: %@", x); // 处理 Next 事件
} error:^(NSError *error) {
NSLog(@"Error: %@", error); // 处理 Error 事件
} completed:^{
NSLog(@"Completed."); // 处理 Completed 事件
}];
输出将会是:
csharp
Starting network request...
// (2秒后)
Received value: Data from server
Completed.
如果订阅被提前销毁(例如持有它的 RACDisposable
被 dispose
了),那么 createSignal:
block 中返回的清理 block 就会被调用。
4. 操作符(Operators)的原理
像 map
, filter
, flattenMap
这样的操作符,其本质是创建了一个新的信号。
- 当你调用
[signalA map:^id(id value) { return [value uppercaseString]; }]
时,会立即返回一个新的信号signalB
。 - 当你订阅
signalB
时,signalB
的内部会去订阅原始的signalA
。 - 当
signalA
发送一个next
事件时,signalB
会捕获这个值,应用map
的转换 block(例如转成大写),然后用转换后的值自己再发送一个新的next
事件 给signalB
的订阅者。
这个过程叫做"链式订阅",每个操作符都是在原信号之上包装了一层新的信号。这保证了信号的不可变性 和链式调用的特性。
总结:RACSignal 的实现原理要点
- 惰性求值 (Lazy Evaluation) : 信号只有在被订阅时才会执行其
didSubscribe
block 中的逻辑。 - 封装副作用 (Side Effect Encapsulation) : 所有会产生副作用的代码(网络、I/O、UI操作)都被封装在
didSubscribe
block 中,并由订阅触发。 - 基于订阅 (Subscription-Based) : 整个数据流的生命周期由订阅开始。订阅时返回的
RACDisposable
用于控制和清理订阅。 - 单向数据流 (Unidirectional Data Flow) : 数据从源头(生产者)通过
subscriber
单向地推送给终点(消费者)。 - 操作符是装饰器 (Operators as Decorators): 操作符通过创建中间信号、订阅上游信号、处理并转发事件来实现,形成了一种装饰器模式。
简单来说,RACSignal 就是一个保存了如何获取数据的指令集(didSubscribe
block),并在有人订阅时执行这些指令的对象。这种设计使得异步操作和事件流能够被优雅地组合、转换和链式调用。