iOS开发基础72-ReactiveObjC :从核心概念到高频使用
ReactiveObjC 深度解析:从核心概念到高频使用与底层原理
ReactiveObjC(简称 RAC)是 GitHub 开源的函数响应式编程(FRP)框架,它将 UI 事件、KVO、通知、代理、网络请求等各种异步操作统一抽象为"信号流",让异步代码像同步代码一样链式书写,极大简化了状态管理和事件处理。本文将从最基础的概念讲起,用通俗易懂的方式带你掌握 RAC 的高频使用场景,并深入其底层原理。
一、ReactiveObjC 简介
1. 什么是 ReactiveObjC
ReactiveObjC 是函数响应式编程(Functional Reactive Programming, FRP)在 Objective-C 上的实现。它的核心思想是:把一切变化都看作数据流(信号),通过操作符对数据流进行变换、过滤、合并,最终订阅数据流来响应变化。
简单来说,传统的 iOS 开发中,我们要处理各种事件:
- 按钮点击 → addTarget:action:
- 文本变化 → delegate
- 属性变化 → KVO
- 通知 → NSNotificationCenter
- 网络请求 → callback block
这些事件的处理方式各不相同,代码分散,状态管理混乱。RAC 把它们全部统一为信号(Signal),用同一套 API 处理:
// 按钮点击
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮被点击了");
}];
// 文本框输入
[[self.textField rac_textSignal] subscribeNext:^(NSString *text) {
NSLog(@"当前输入: %@", text);
}];
// KVO
[RACObserve(self.user, name) subscribeNext:^(NSString *name) {
NSLog(@"用户名变化: %@", name);
}];
2. RAC 解决了什么问题
| 传统开发的痛点 | RAC 的解决方案 |
|---|---|
| 事件处理方式不统一(target/delegate/KVO/通知/callback) | 统一为信号,一套 API 处理所有事件 |
| 状态管理混乱,多个变量相互依赖 | 信号流自动传播变化,无需手动管理状态 |
| 异步回调嵌套(callback hell) | 链式调用,代码扁平化 |
| 跨模块通信耦合 | 信号可以自由传递和组合,降低耦合 |
| 错误处理分散 | 信号统一处理 error 事件 |
3. 安装
通过 CocoaPods 安装:
pod 'ReactiveObjC', '~> 3.1'
导入头文件:
#import <ReactiveObjC/ReactiveObjC.h>
二、核心概念
1. 信号(RACSignal)
信号是 RAC 的核心,它代表一个"未来会产生一系列值的数据流"。信号本身不会主动发送值,只有当被订阅时才会开始发送。
信号可以发送三种事件:
next:发送一个新值(可以是任意对象)error:发送一个错误,信号终止completed:信号正常完成,终止
时间轴 ──────────────────────────────────▶
next(1) next(2) next(3) completed
│ │ │ │
▼ ▼ ▼ ▼
订阅者收到: 1 2 3 结束
2. 订阅者(RACSubscriber)
订阅者是信号的接收者。当你调用 subscribeNext: 时,RAC 内部会创建一个订阅者,接收信号发送的 next/error/completed 事件。
3. 冷信号 vs 热信号
| 类型 | 特点 | 示例 |
|---|---|---|
| 冷信号(Cold Signal) | 每次订阅都会重新开始发送值,不订阅就不发送 | 网络请求、文件读取、定时器(RAC 中大部分信号) |
| 热信号(Hot Signal) | 事件的产生与订阅无关,订阅者只能收到订阅之后的事件 | UI 事件(按钮点击)、通知 |
大部分 RACSignal 是冷信号。UI 事件类的信号(如 rac_signalForControlEvents)虽然表现为热信号,但底层也是通过冷信号 + 共享订阅实现的。
4. RACStream(流)
RACStream 是 RACSignal 和 RACSequence 的父类,定义了通用的流操作(如 map、filter、flattenMap 等)。可以理解为:RACSignal 是随时间推送值的流,RACSequence 是空间上排列值的流(类似数组)。
三、基础使用(高频场景)
1. 按钮点击事件
#import <ReactiveObjC/ReactiveObjC.h>
// 监听按钮点击
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(UIButton *button) {
NSLog(@"登录按钮被点击");
[self login];
}];
2. 文本框输入监听
// 监听文本框输入(每次输入变化都会触发)
[[self.usernameTextField rac_textSignal]
subscribeNext:^(NSString *text) {
NSLog(@"用户名: %@", text);
}];
// 也可以用 rac_valuesForKeyPath:observer: 监听 text 属性
3. KVO(属性变化监听)
// RACObserve 是宏,等价于 [self.user rac_valuesForKeyPath:@"name" observer:self]
[RACObserve(self.user, name)
subscribeNext:^(NSString *newName) {
NSLog(@"用户名从旧值变为: %@", newName);
}];
// 监听 self.view 的 frame 变化
[RACObserve(self.view, frame)
subscribeNext:^(NSValue *frameValue) {
NSLog(@"view frame: %@", frameValue);
}];
RACObserve(TARGET, KEYPATH)是 RAC 最常用的宏之一,它会自动管理 KVO 的注册和移除,无需手动 removeObserver。
4. 通知
// 监听 UIKeyboardWillShowNotification
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil]
subscribeNext:^(NSNotification *notification) {
NSDictionary *userInfo = notification.userInfo;
NSValue *keyboardFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey];
CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
NSLog(@"键盘高度: %f", keyboardFrame.size.height);
}];
RAC 的通知信号在订阅者释放时自动移除观察者,无需手动 removeObserver。
5. 代理方法
RAC 可以把 delegate 方法转化为信号。以 UITextFieldDelegate 为例:
// 监听 textFieldShouldReturn:(点击键盘 return 键)
[[self.textField rac_signalForSelector:@selector(textFieldShouldReturn:)]
subscribeNext:^(RACTuple *tuple) {
// tuple 中包含代理方法的参数
UITextField *textField = tuple.first;
NSLog(@"点击了 return 键: %@", textField.text);
[textField resignFirstResponder];
}];
rac_signalForSelector:会自动把对象设置为自己的 delegate(如果对象遵循了对应协议),代理方法被调用时转化为信号发送值。参数打包在RACTuple中。
6. 定时器
// 每隔 1 秒发送一个值,从 0 开始递增
[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]
subscribeNext:^(NSDate *date) {
NSLog(@"定时触发: %@", date);
}];
// 带自动计数的定时器(用 scan 累加)
[[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]
scanWithStart:@(0) reduce:^NSNumber *(NSNumber *running, NSDate *date) {
return @(running.integerValue + 1);
}]
subscribeNext:^(NSNumber *count) {
NSLog(@"第 %@ 秒", count);
}];
7. 手势识别
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[self.view addGestureRecognizer:tap];
[[tap rac_gestureSignal]
subscribeNext:^(UITapGestureRecognizer *gesture) {
CGPoint location = [gesture locationInView:self.view];
NSLog(@"点击位置: %@", NSStringFromCGPoint(location));
}];
四、RAC 宏(高频使用)
1. RACObserve(KVO 监听)
// 监听 self.user 的 name 属性变化
[RACObserve(self.user, name) subscribeNext:^(id x) { ... }];
2. RAC(绑定属性)
RAC(TARGET, KEYPATH) 宏把信号的值自动绑定到对象的属性上,信号每次发送 next,属性就自动更新:
// 把文本框的输入实时绑定到 label 的 text
RAC(self.nameLabel, text) = self.usernameTextField.rac_textSignal;
// 把用户名是否为空绑定到按钮的 enabled 属性
RAC(self.loginButton, enabled) = [RACSignal combineLatest:@[
self.usernameTextField.rac_textSignal,
self.passwordTextField.rac_textSignal
] reduce:^id(NSString *username, NSString *password) {
return @(username.length > 0 && password.length > 0);
}];
RAC宏是 RAC 最强大的特性之一,实现了数据的单向绑定,无需手动在回调中赋值。
3. @weakify / @strongify(避免循环引用)
RAC 中的 block 容易造成循环引用,RAC 提供了 @weakify 和 @strongify 宏来安全处理:
@weakify(self);
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
@strongify(self);
if (!self) return;
[self login];
}];
@weakify(self)把 self 转为 weak 引用,@strongify(self)在 block 内重新转为 strong 引用(局部变量),避免循环引用,同时防止 self 在 block 执行过程中被释放。
4. RACCommand(命令封装)
RACCommand 封装了一个动作的执行,常用于按钮点击触发网络请求等场景,它可以自动管理按钮的 enabled 状态和执行状态:
// 创建一个命令,执行时返回一个信号(如网络请求)
self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行登录");
return [self loginRequestSignal]; // 返回网络请求信号
}];
// 把命令绑定到按钮(点击按钮自动执行命令,执行期间按钮自动禁用)
self.loginButton.rac_command = self.loginCommand;
// 监听命令执行结果
[self.loginCommand.executionSignals.switchToLatest
subscribeNext:^(id response) {
NSLog(@"登录成功: %@", response);
}];
// 监听错误
[self.loginCommand.errors subscribeNext:^(NSError *error) {
NSLog(@"登录失败: %@", error);
}];
五、高阶操作符(高频使用)
操作符是 RAC 的灵魂,它们对信号流进行变换、过滤、合并。以下是最常用的操作符。
1. map(映射)
把信号发送的每个值转换为另一个值:
// 把文本输入映射为文本长度
[[self.textField.rac_textSignal
map:^id(NSString *text) {
return @(text.length);
}]
subscribeNext:^(NSNumber *length) {
NSLog(@"文本长度: %@", length);
}];
2. filter(过滤)
只让满足条件的值通过:
// 只接收长度大于 3 的输入
[[self.textField.rac_textSignal
filter:^BOOL(NSString *text) {
return text.length > 3;
}]
subscribeNext:^(NSString *text) {
NSLog(@"有效输入: %@", text);
}];
3. flattenMap(扁平映射)
map 返回的是值,flattenMap 返回的是信号,它会把内部信号的值"拍平"到外层信号中。最常用于网络请求链式调用:
// 点击登录按钮 → 发起登录请求 → 登录成功后获取用户信息
[[[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^RACStream *(id value) {
// 返回登录请求信号
return [self loginRequestSignal];
}]
flattenMap:^RACStream *(id loginResponse) {
// 登录成功后,返回获取用户信息的请求信号
return [self fetchUserInfoSignal];
}]
subscribeNext:^(id userInfo) {
NSLog(@"获取用户信息成功: %@", userInfo);
} error:^(NSError *error) {
NSLog(@"出错: %@", error);
}];
flattenMap是 RAC 中最重要的操作符之一,它解决了异步回调嵌套问题,让异步代码像同步代码一样链式书写。
4. combineLatest(合并最新值)
合并多个信号的最新值,当任意一个信号发送新值时,用所有信号的最新值组合发送:
// 用户名和密码都有值时,登录按钮才可用
RAC(self.loginButton, enabled) = [RACSignal
combineLatest:@[self.usernameTextField.rac_textSignal,
self.passwordTextField.rac_textSignal]
reduce:^id(NSString *username, NSString *password) {
return @(username.length >= 3 && password.length >= 6);
}];
combineLatest:reduce:是表单验证的利器,多个输入字段的状态自动联动。
5. zip(压缩)
把多个信号的值按顺序一一配对,只有当所有信号都发送了第 N 个值时,才发送第 N 个组合值:
// 两个请求都完成后才触发
[[RACSignal zip:@[
[self fetchUserInfoSignal],
[self fetchConfigSignal]
]] subscribeNext:^(RACTuple *tuple) {
id userInfo = tuple.first;
id config = tuple.second;
NSLog(@"两个请求都完成了");
}];
zip与combineLatest的区别:zip 按顺序一一配对(第1个配第1个),combineLatest 总是用最新值组合。
6. merge(合并)
把多个信号合并为一个信号,任意一个信号发送值,合并后的信号就发送:
// 合并两个按钮的点击事件
RACSignal *merged = [RACSignal merge:@[
[self.button1 rac_signalForControlEvents:UIControlEventTouchUpInside],
[self.button2 rac_signalForControlEvents:UIControlEventTouchUpInside]
]];
[merged subscribeNext:^(UIButton *button) {
NSLog(@"点击了按钮: %@", button);
}];
7. take / skip(取前 N 个 / 跳过前 N 个)
// 只取前 3 次输入
[[self.textField.rac_textSignal take:3]
subscribeNext:^(NSString *text) {
NSLog(@"前3次输入: %@", text);
}];
// 跳过前 2 次,从第 3 次开始
[[self.textField.rac_textSignal skip:2]
subscribeNext:^(NSString *text) {
NSLog(@"从第3次开始: %@", text);
}];
8. throttle / debounce(节流 / 防抖)
throttle::在指定时间间隔内,只发送最新的值(忽略中间值)。delay:是延迟发送,不是防抖。RAC 中防抖常用throttle:实现。
// 搜索框防抖:用户停止输入 0.5 秒后才触发搜索
[[[self.searchTextField.rac_textSignal
throttle:0.5]
flattenMap:^RACStream *(NSString *keyword) {
return [self searchSignalWithKeyword:keyword];
}]
subscribeNext:^(id results) {
NSLog(@"搜索结果: %@", results);
}];
防抖是搜索框的经典优化,避免用户每输入一个字就发一次请求。
9. distinctUntilChanged(去重)
只有当新值与上一个值不同时才发送:
// 忽略重复的输入(如连续输入相同内容)
[[self.textField.rac_textSignal distinctUntilChanged]
subscribeNext:^(NSString *text) {
NSLog(@"变化后的输入: %@", text);
}];
10. switchToLatest(切换到最新)
用于信号的信号(signal of signals),只订阅最新的内部信号,取消之前的订阅:
// 每次点击搜索按钮,发起新请求;如果新请求发起时旧请求还没完成,自动取消旧请求
[[[[self.searchButton rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id value) {
return [self searchSignal]; // 返回请求信号
}]
switchToLatest]
subscribeNext:^(id results) {
NSLog(@"最新搜索结果: %@", results);
}];
switchToLatest常用于搜索场景,自动取消过期的旧请求,只保留最新请求的结果。
六、实战场景
1. 登录表单完整逻辑
- (void)setupLoginLogic {
@weakify(self);
// 1. 用户名密码都有效时,登录按钮才可点击
RAC(self.loginButton, enabled) = [RACSignal
combineLatest:@[self.usernameField.rac_textSignal,
self.passwordField.rac_textSignal]
reduce:^id(NSString *username, NSString *password) {
return @(username.length >= 3 && password.length >= 6);
}];
// 2. 登录命令
self.loginCommand = [[RACCommand alloc] initWithEnabled:self.loginButton.rac_enabled
signalBlock:^RACSignal *(id input) {
@strongify(self);
return [self loginRequestSignal];
}];
self.loginButton.rac_command = self.loginCommand;
// 3. 监听登录结果
[self.loginCommand.executionSignals.switchToLatest
subscribeNext:^(id response) {
@strongify(self);
NSLog(@"登录成功");
[self.navigationController popViewControllerAnimated:YES];
}];
[self.loginCommand.errors
subscribeNext:^(NSError *error) {
NSLog(@"登录失败: %@", error.localizedDescription);
}];
// 4. 执行中显示 loading
RAC(self, isLoading) = self.loginCommand.executing;
}
2. 搜索框防抖 + 自动取消旧请求
- (void)setupSearchLogic {
@weakify(self);
[[[[self.searchField.rac_textSignal
// 跳过初始空值
skip:1]
// 防抖 0.5 秒
throttle:0.5]
// 去重
distinctUntilChanged]
subscribeNext:^(NSString *keyword) {
@strongify(self);
[self searchWithKeyword:keyword];
}];
}
3. 网络请求链式调用(登录 → 获取用户信息 → 获取配置)
- (void)loginAndFetchData {
@weakify(self);
[[[[self loginSignal]
flattenMap:^RACStream *(id loginResponse) {
@strongify(self);
return [self fetchUserInfoSignal];
}]
flattenMap:^RACStream *(id userInfo) {
@strongify(self);
return [self fetchConfigSignal];
}]
subscribeNext:^(id config) {
NSLog(@"全部数据加载完成");
} error:^(NSError *error) {
NSLog(@"出错: %@", error);
}];
}
七、底层原理
1. RACSignal 的本质
RACSignal 是一个对象,它的核心是一个 didSubscribe block。当信号被订阅时,这个 block 被执行,block 内部可以向订阅者发送 next/error/completed 事件。
简化的核心结构:
@interface RACSignal ()
@property (nonatomic, copy) void (^didSubscribe)(id<RACSubscriber> subscriber);
@end
@implementation RACSignal
+ (RACSignal *)createSignal:(void (^)(id<RACSubscriber> subscriber))didSubscribe {
RACSignal *signal = [[RACSignal alloc] init];
signal.didSubscribe = didSubscribe;
return signal;
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
// 订阅时执行 didSubscribe block
self.didSubscribe(subscriber);
// 返回 disposable,用于取消订阅
return [RACDisposable disposableWithBlock:^{
// 清理工作
}];
}
@end
2. 订阅流程
// 1. 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 3. 订阅时执行这里
[subscriber sendNext:@"hello"];
[subscriber sendNext:@"world"];
[subscriber sendCompleted];
return nil;
}];
// 2. 订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"收到: %@", x);
}];
执行流程:
createSignal:创建信号对象,保存didSubscribeblock,不执行任何操作。subscribeNext:创建订阅者,调用[signal subscribe:subscriber]。subscribe:内部执行保存的didSubscribeblock,传入订阅者。didSubscribeblock 中调用[subscriber sendNext:],触发订阅者的 next block。sendCompleted后,信号终止,自动清理资源。
3. 操作符的实现原理
操作符(如 map、filter、flattenMap)本质上是创建一个新的信号,在新信号的 didSubscribe 中订阅原信号,并在原信号的事件中做变换后转发给新信号的订阅者。
以 map 为例的简化实现:
- (RACSignal *)map:(id (^)(id value))block {
// 创建一个新信号
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 在新信号的 didSubscribe 中,订阅原信号(self)
return [self subscribeNext:^(id x) {
// 原信号发送值时,用 block 变换后转发给新信号的订阅者
id transformed = block(x);
[subscriber sendNext:transformed];
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
}];
}
这就是为什么操作符可以链式调用:每个操作符都返回一个新的 RACSignal,新信号又可以继续调用操作符。
4. 冷信号的惰性执行
RACSignal 是冷信号,意味着:
- 创建信号时不执行任何操作(不发请求、不计时)。
- 只有被订阅时才开始执行(发请求、开始计时)。
- 每次订阅都会重新执行(如网络请求信号,订阅两次就发两次请求)。
// 创建信号时不发请求
RACSignal *requestSignal = [self networkRequestSignal];
// 第一次订阅 → 发第一次请求
[requestSignal subscribeNext:^(id x) { ... }];
// 第二次订阅 → 发第二次请求
[requestSignal subscribeNext:^(id x) { ... }];
如果希望多个订阅者共享一次执行(热信号),可以用 multicast: 或 replay 操作符。
5. 内存管理(自动取消订阅)
RAC 的内存管理设计精巧:
- 信号被订阅者持有(订阅者在订阅时持有信号的引用)。
- 信号在
didSubscribe中持有订阅者(通过 block 捕获)。 - 这形成了一个循环引用,但 RAC 在信号完成(completed)或错误(error)时,会自动断开这个循环引用,释放资源。
- 如果信号永远不完成(如 UI 事件信号),订阅者会一直持有信号,直到订阅者自己被释放(此时 disposable 被取消,循环引用断开)。
// 订阅返回的 disposable 可以手动取消
RACDisposable *disposable = [signal subscribeNext:^(id x) { ... }];
// 手动取消订阅(如页面消失时)
[disposable dispose];
6. RACScheduler(调度器)
RACScheduler 是 RAC 的线程调度器,类似 GCD 的队列。常用的调度器:
| 调度器 | 说明 |
|---|---|
+[RACScheduler mainThreadScheduler] |
主线程调度器,用于 UI 更新 |
+[RACScheduler currentScheduler] |
当前线程调度器 |
+[RACScheduler scheduler] |
创建一个后台串行队列调度器 |
+[RACScheduler immediateScheduler] |
立即执行(不切换线程) |
用 subscribeOn: 和 deliverOn: 控制线程:
[[[networkRequestSignal
subscribeOn:[RACScheduler scheduler]] // 在后台线程执行请求
deliverOn:[RACScheduler mainThreadScheduler]] // 结果回主线程
subscribeNext:^(id response) {
// 主线程更新 UI
self.label.text = response;
}];
八、优缺点与注意事项
1. 优点
- 统一事件处理:UI 事件、KVO、通知、代理、网络请求全部统一为信号,一套 API。
- 链式调用:异步代码扁平化,避免回调嵌套(callback hell)。
- 数据绑定:RAC 宏实现属性自动绑定,减少手动赋值代码。
- 组合能力强:combineLatest、zip、merge 等操作符轻松组合多个异步流。
- 自动内存管理:信号完成后自动释放,KVO/通知自动移除观察者。
2. 缺点
- 学习曲线陡峭:函数响应式编程范式与传统命令式编程差异大,操作符多,入门难。
- 调试困难:信号流是异步的、链式的,断点调试不直观,出错时难以定位。
- 性能开销:信号和操作符会创建大量对象,高频场景(如滚动)可能有性能问题。
- 代码可读性:不熟悉 RAC 的人难以读懂链式调用代码。
- OC 版本维护减少:ReactiveObjC 已进入维护模式,新项目可考虑 Swift 的 Combine 或 RxSwift。
3. 注意事项
- 循环引用:block 中引用 self 必须用
@weakify/@strongify。 - 冷信号重复执行:网络请求信号被多次订阅会发多次请求,注意共享或缓存。
- 信号必须完成或被取消:否则可能造成内存泄漏。
- 线程切换:UI 更新必须在主线程,用
deliverOn:mainThreadScheduler。 - 避免过度使用:简单的事件处理用传统方式即可,RAC 适合复杂的异步流组合场景。
九、Swift 对应框架
| 概念 | ReactiveObjC(OC) | RxSwift(Swift) | Combine(Apple 原生,iOS 13+) |
|---|---|---|---|
| 信号 | RACSignal | Observable | Publisher |
| 订阅者 | RACSubscriber | Observer / Subscriber | Subscriber |
| 订阅 | subscribeNext: | subscribe(onNext:) | sink(receiveValue:) |
| 映射 | map: | map | map |
| 扁平映射 | flattenMap: | flatMap | flatMap |
| 合并 | combineLatest: | combineLatest | combineLatest |
| 压缩 | zip: | zip | zip |
| 防抖 | throttle: | debounce / throttle | debounce / throttle |
| KVO | RACObserve | - | @Published / observe |
| 调度器 | RACScheduler | Scheduler | Scheduler / DispatchQueue |
如果你熟悉了 ReactiveObjC 的概念,学习 RxSwift 或 Combine 会非常轻松,核心思想完全一致,只是 API 名称不同。
十、总结
- ReactiveObjC 是什么:函数响应式编程框架,把一切异步事件(UI 事件、KVO、通知、代理、网络请求)统一抽象为信号(RACSignal),通过操作符变换、过滤、合并信号流,订阅信号来响应变化。
- 核心概念:信号(RACSignal,发送 next/error/completed)、订阅者(RACSubscriber)、冷信号(订阅才执行,每次订阅重新执行)、热信号(事件与订阅无关)、RACStream(流的父类)。
- 高频基础使用:按钮点击 rac_signalForControlEvents、文本输入 rac_textSignal、KVO RACObserve、通知 rac_addObserverForName、代理 rac_signalForSelector、定时器 RACSignal interval:、手势 rac_gestureSignal。
- 高频宏:RACObserve(KVO)、RAC(属性绑定)、@weakify/@strongify(防循环引用)、RACCommand(命令封装,自动管理 enabled 和执行状态)。
- 高频操作符:map(映射)、filter(过滤)、flattenMap(扁平映射,异步链式调用核心)、combineLatest(合并最新值,表单验证)、zip(按序压缩)、merge(合并)、take/skip(取/跳)、throttle(防抖)、distinctUntilChanged(去重)、switchToLatest(切换最新,自动取消旧请求)。
- 底层原理:RACSignal 核心是 didSubscribe block,订阅时执行;操作符创建新信号,在新信号的 didSubscribe 中订阅原信号并变换转发;冷信号惰性执行;信号完成/错误时自动断开循环引用释放资源;RACScheduler 管理线程调度。
- 实战场景:登录表单(combineLatest 验证 + RACCommand 执行)、搜索框防抖(throttle + switchToLatest)、网络请求链式调用(flattenMap 嵌套)。
- 选型建议:ReactiveObjC 适合复杂异步流组合、多状态联动的场景;简单事件用传统方式即可;新项目可考虑 Swift 的 Combine(Apple 原生)或 RxSwift。

浙公网安备 33010602011771号