RACSignal实现原理

我们来深入剖析一下 RACSignal 的实现原理

RACSignal 是 ReactiveCocoa 中最核心的类之一,它代表了一个异步数据流 。理解其原理对于掌握 ReactiveCocoa 至关重要。它的实现可以概括为:基于订阅的、惰性的、单向的、推送式的数据流

其核心原理主要围绕以下几个关键点展开:


1. 核心思想:Pull vs. Push

  • Pull (拉取): 传统的编程模式。消费者在需要值时主动去获取(例如,访问一个属性、调用一个函数)。控制权在消费者手中。
  • Push (推送): 响应式编程模式。生产者在你不知道的时候"推送"值给你。你只需要订阅它,并在值到来时做出反应。控制权在生产者手中。

RACSignal 就是一个 Push 模式的实现。你无法主动要求一个 Signal 给你值,你只能订阅它,然后等待它将来某个时间点可能给你发送值。


2. 核心机制:订阅(Subscription)

这是 RACSignal 最精髓的部分。一个 Signal 在被订阅前,什么都不做(惰性求值)。只有当 subscribeNext:error:completed: 被调用时,它才开始执行它的工作(例如发起网络请求、监听通知等)。

订阅过程可以抽象为以下步骤:

  1. 创建信号 (Create) : 使用 [RACSignal createSignal:] 类方法创建一个信号。这个方法的参数是一个 block,我们称之为 didSubscribe block。
  2. 订阅信号 (Subscribe) : 调用者对这个信号执行 subscribeNext:... 方法。
  3. 触发执行 (Trigger)subscribeNext:... 方法内部会去执行第一步中创建的 didSubscribe block。
  4. 传递事件 (Send Events) : 在 didSubscribe block 中,你可以通过提供的 subscriber 对象发送 next, error, completed 事件。
  5. 处理事件 (Handle Events)subscribeNext:... 方法中传入的 blocks 会相应地处理这些事件。
  6. 销毁清理 (Dispose) : 当信号发送 errorcompleted 事件,或者订阅被手动取消时,会执行清理工作。

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.

如果订阅被提前销毁(例如持有它的 RACDisposabledispose 了),那么 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 的实现原理要点

  1. 惰性求值 (Lazy Evaluation) : 信号只有在被订阅时才会执行其 didSubscribe block 中的逻辑。
  2. 封装副作用 (Side Effect Encapsulation) : 所有会产生副作用的代码(网络、I/O、UI操作)都被封装在 didSubscribe block 中,并由订阅触发。
  3. 基于订阅 (Subscription-Based) : 整个数据流的生命周期由订阅开始。订阅时返回的 RACDisposable 用于控制和清理订阅。
  4. 单向数据流 (Unidirectional Data Flow) : 数据从源头(生产者)通过 subscriber 单向地推送给终点(消费者)。
  5. 操作符是装饰器 (Operators as Decorators): 操作符通过创建中间信号、订阅上游信号、处理并转发事件来实现,形成了一种装饰器模式。

简单来说,RACSignal 就是一个保存了如何获取数据的指令集(didSubscribe block),并在有人订阅时执行这些指令的对象。这种设计使得异步操作和事件流能够被优雅地组合、转换和链式调用。

相关推荐
四月_h6 分钟前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_4275060812 分钟前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。1 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
lecepin2 小时前
AI Coding 资讯 2025.8.27
前端·ai编程
TimelessHaze2 小时前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯3 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越3 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区3 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
qfZYG3 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器
bug爱好者3 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript