Android / iOS / 鸿蒙 / React / Flutter 响应式编程共同点分析

由浅入深,从基本概念到源码原理,再到实战案例,系统梳理五大主流开发框架中的响应式编程共性与差异


一、什么是响应式编程?

1.1 从命令式到声明式

无论使用哪个平台,传统命令式编程的本质都是「我告诉程序每一步该做什么」:

1
2
3
4
5
6
7
8
9
// Android 命令式:手动监听 + 条件判断
editText.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {
val text = text.toString()
if (text.length >= 3) {
searchUsers(text)
}
}
})
1
2
3
4
5
6
// iOS 命令式:Target-Action
textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged)
func textDidChange() {
let text = textField.text ?? ""
if text.count >= 3 { searchUsers(text) }
}

响应式编程则将数据流(Data Stream)视为核心,用声明式方式描述「当数据变化时该做什么」:

1
2
命令式:监听 → 判断 → 执行
响应式:数据流 → 转换/过滤 → 订阅并响应

1.2 响应式编程的三大特征

特征 说明 跨平台体现
数据流 事件、状态、异步结果统一抽象为「流」 Observable / Signal / Stream / State
声明式 描述「是什么」而非「怎么做」 链式操作符、装饰器、Hooks
自动传播 依赖变化自动触发更新 订阅机制、依赖收集、重新渲染

1.3 五大平台的响应式方案概览

平台 主要方案 核心类型 特点
Android RxJava / Kotlin Flow Observable / Flow 操作符丰富,协程整合
iOS RAC / RxSwift Signal / Observable 热/冷信号、Cocoa 扩展
鸿蒙 ArkUI @State / @Observed 声明式 UI,装饰器驱动
React Hooks useState / useEffect 函数式、虚拟 DOM 驱动
Flutter Stream / ValueNotifier Stream / ChangeNotifier Dart 异步流、Listenable

二、核心概念共通点

2.1 观察者模式:统一的底层基石

所有响应式实现都以观察者模式为基础:有「被观察对象」和「观察者」,数据变化时通知观察者。

1
2
3
4
5
6
7
┌─────────────────┐         ┌──────────────────┐
│ 数据源/生产者 │ ──────► │ 观察者/消费者 │
│ (Observable等) │ 订阅 │ (Observer等) │
└─────────────────┘ └──────────────────┘
│ ▲
│ onNext / send / emit │
└────────────────────────────┘
平台 被观察者 观察者 订阅方式
Android RxJava Observable Observer subscribe()
Android Flow Flow Collector collect {}
iOS RAC Signal / SignalProducer Observer observe() / start()
React useState 返回值 组件 隐式(依赖 React 调度)
Flutter Stream / ValueNotifier StreamSubscription / addListener listen() / addListener()
鸿蒙 @State 变量 UI 组件 隐式(框架自动重绘)

2.2 可取消性(Disposable / 生命周期)

订阅通常会产生「可取消」的句柄,用于在合适时机释放资源,避免内存泄漏:

平台 取消句柄 典型用法
RxJava Disposable compositeDisposable.add()
Kotlin Flow Job / CoroutineScope viewModelScope.launch {}
RAC Disposable disposable.dispose()
React useEffect 清理函数 return () => cleanup()
Flutter StreamSubscription subscription.cancel()
鸿蒙 框架管理 组件销毁自动解绑

2.3 热流 vs 冷流(部分平台)

在 RxJava、RAC、Kotlin Flow 中,流有「热」「冷」之分:

类型 含义 典型场景
冷流 每次订阅才执行,每个订阅者独立收到完整数据 网络请求、文件读取
热流 无论是否订阅都会持续发送,多订阅共享同一流 按钮点击、通知、UI 事件

React / Flutter / 鸿蒙 的「状态」更接近热流:始终有一个当前值,变化时通知依赖方。


三、各平台实现原理浅析

3.1 Android:RxJava 与 Kotlin Flow

RxJava:基于 ReactiveX 规范,Observable 链式操作符,每次操作返回新 Observable,订阅时从上游向下游传递事件。

1
2
3
4
5
6
7
8
9
10
11
// RxJava 链式调用
Observable.create<String> { emitter ->
emitter.onNext("Hello")
emitter.onComplete()
}
.map { it.uppercase() }
.filter { it.length > 0 }
.subscribe(
{ println(it) },
{ it.printStackTrace() }
)

Kotlin Flow:冷流,基于协程,背压天然支持,与 StateFlow/SharedFlow 配合做 UI 状态和事件。

1
2
3
4
5
// Flow 冷流 + 操作符
flowOf(1, 2, 3)
.map { it * 2 }
.filter { it > 2 }
.collect { println(it) }

LiveData:轻量级、生命周期感知,主线程回调,适合简单 UI 状态,官方推荐新项目用 Flow 替代。

3.2 iOS:ReactiveCocoa / ReactiveSwift

Signal:热信号,由 pipe() 创建,observer 控制发送。

1
2
3
let (signal, observer) = Signal<String, Never>.pipe()
signal.observeValues { print($0) }
observer.send(value: "Hi")

SignalProducer:冷信号,每次 start 执行一次,适合异步任务。

1
2
3
4
5
let producer = SignalProducer<String, Never> { obs, _ in
obs.send(value: "Hello")
obs.sendCompleted()
}
producer.startWithValues { print($0) }

3.3 鸿蒙 ArkUI:装饰器驱动的状态

ArkUI 用装饰器声明「可观察」的状态,状态变化自动触发 UI 更新:

1
2
3
4
5
6
7
8
9
10
11
12
// @State:组件内部状态
@State count: number = 0

// @Prop:父→子单向
// @Link:双向
// @Provide / @Consume:跨层级

// @Observed + @ObjectLink:嵌套对象
@Observed
class User {
name: string
}

@ObservedV2 + @Trace(V2):支持深层属性观察,解决嵌套对象不可观察的问题。

3.4 React:状态与副作用

React 的响应式体现在「状态驱动 UI」和「副作用与依赖同步」:

1
2
3
4
5
6
7
8
9
10
11
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

// 依赖 keyword 变化,自动重新执行
useEffect(() => {
if (keyword.length < 3) return;
const timer = setTimeout(() => {
searchUsers(keyword).then(setUsers);
}, 300);
return () => clearTimeout(timer); // 清理
}, [keyword]);

状态变化 → 重新渲染 → 虚拟 DOM diff → 最小化 DOM 更新。

3.5 Flutter:Stream 与 Listenable

Stream:Dart 异步流,类似冷流,支持 map、where、asyncMap 等。

1
2
3
4
Stream.periodic(Duration(seconds: 1))
.map((i) => i * 2)
.take(5)
.listen((value) => print(value));

ValueNotifier / ChangeNotifier:同步、轻量,配合 ValueListenableBuilder 做局部重建:

1
2
3
4
5
final counter = ValueNotifier<int>(0);
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (_, value, __) => Text('$value'),
)

四、操作符与组合逻辑的共性

4.1 转换类:map / filter

操作符 含义 Android iOS RAC Flutter React
map 值转换 map {} .map {} .map() 派生 state
filter 过滤 filter {} .filter {} .where() 条件 + early return

4.2 组合类:combineLatest / merge

多流组合在各平台均有对应能力:

场景 RxJava RAC Flutter React
多源最新值 combineLatest combineLatest RxDart combineLatest2 多个 useState + useEffect
多流合并 merge merge StreamGroup.merge 自定义逻辑

4.3 扁平化:flatMap / switchMap

将「每个值 → 新流」展开,常用于搜索联想、取消旧请求:

平台 操作符 行为
RxJava flatMap / switchMap switchMap 只保留最新内层流
RAC flatMap(.latest) 新关键词取消上次请求
Flutter asyncExpand 类似 flatMap
React useEffect + 清理 依赖变化时清理上次 effect

4.4 时间控制:debounce / throttle

防抖、节流在搜索、滚动等场景通用:

平台 防抖 节流
RxJava debounce() throttleFirst/throttleLatest
RAC debounce() throttle()
Flutter debounce from rxdart throttle from rxdart
React 自定义 setTimeout + cleanup useThrottle / lodash

五、源码层面的共通原理

5.1 链式结构与包装

操作符通常不修改原流,而是返回新的「包装流」,内部订阅上游并转换后传给下游:

1
2
3
4
5
6
7
8
9
Observable.map(f)

├─ 创建 MapObservable
│ │
│ ├─ source = 上游 Observable
│ └─ mapper = 转换函数 f

└─ subscribe 时:上游.subscribe(下游的包装 Observer)
下游 Observer 收到值时:onNext(mapper(value))

RAC 的 map 也是类似结构:Signal { observer in self.observe { ... observer.send(value: transform($0)) } }

5.2 订阅传播

订阅是「从下游往上游」建立,事件是「从上游往下游」传递:

1
2
3
4
5
6
subscribe(observer)
→ 下游 Observable 订阅其 source
→ 再往上游订阅
→ … 直到最顶层
→ 顶层开始 onNext/onComplete
→ 层层传递到最下游

5.3 取消传播

Disposable / Job 形成链:取消下游会向上传递,终止整条链的执行。


六、跨平台示例:搜索防抖 + 取消旧请求

6.1 Android (RxJava)

1
2
3
4
5
6
7
8
RxTextView.textChanges(searchEditText)
.map { it.toString() }
.filter { it.length >= 2 }
.debounce(300, TimeUnit.MILLISECONDS)
.switchMap { keyword -> api.searchUsers(keyword).toObservable() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ users -> updateUI(users) }, { it.printStackTrace() })
.addTo(compositeDisposable)

6.2 iOS (RAC)

1
2
3
4
5
6
7
8
9
10
searchTextField.reactive.continuousTextValues
.filter { ($0 ?? "").count >= 2 }
.debounce(0.3, on: QueueScheduler.main)
.flatMap(.latest) { keyword in
searchAPI(keyword ?? "")
}
.observe(on: UIScheduler())
.observeValues { [weak self] users in
self?.updateSearchResults(users)
}

6.3 鸿蒙 (ArkUI)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@State keyword: string = ''
@State users: User[] = []
private timer: number = -1

onInputChange(value: string) {
this.keyword = value
clearTimeout(this.timer)
if (value.length < 2) return
this.timer = setTimeout(() => {
SearchAPI.searchUsers(value).then((users: User[]) => {
this.users = users
})
}, 300)
}

6.4 React

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
let cancelled = false;
searchUsers(keyword).then(data => {
if (!cancelled) setUsers(data);
});
return () => { cancelled = true; };
}, 300);
return () => clearTimeout(timer);
}, [keyword]);

6.5 Flutter

1
2
3
4
5
6
7
8
9
10
11
12
13
final _keyword = StreamController<String>.broadcast();
final _users = ValueNotifier<List<User>>([]);

_keyword.stream
.where((s) => s.length >= 2)
.transform(StreamTransformer.fromHandlers(
handleData: (data, sink) => Timer(Duration(milliseconds: 300), () => sink.add(data)),
))
.asyncMap((k) => searchAPI(k))
.listen((users) => _users.value = users);

// 输入时
_keyword.add(controller.text);

七、实际项目应用案例

7.1 登录表单校验(多字段组合)

需求:用户名 ≥3 字符、密码 ≥6 字符时,登录按钮才可点击。

平台 实现要点
RxJava combineLatest(username, password).map { u, p -> u.length>=3 && p.length>=6 }
RAC Signal.combineLatest(username.signal, password.signal).map { … }
React useMemo 派生 canLogin = user.length>=3 && pwd.length>=6
Flutter ValueNotifier 或 Stream,combineLatest2 派生
鸿蒙 @State user/pwd,computed 或 @Computed 派生 canLogin

7.2 多数据源合并展示

需求:本地缓存 + 网络接口合并去重后展示。

各平台通用思路:

  1. 本地流 + 远程流
  2. combineLatest / zip / merge 合并
  3. map 去重、排序
  4. 订阅结果更新 UI

7.3 列表下拉刷新 + 分页加载

需求:下拉刷新、上拉加载更多,防重复请求。

  • Subject / PublishSubject 表示下拉、触底事件
  • debounce 防抖
  • flatMap/switchMap 发起请求,合并到单一列表流
  • 错误重试、loading 状态管理

7.4 电商 App 购物车总价

需求:商品数量、价格变化时,实时计算总价。

  • 每个商品的数量、单价作为流/状态
  • combineLatest 合并所有项
  • map/reduce 计算总价
  • 单一订阅更新总价 UI

八、各平台选型建议

场景 Android iOS 鸿蒙 React Flutter
新项目 优先 Kotlin Flow RAC/RxSwift ArkUI 原生 Hooks Stream + ValueNotifier
复杂异步流 RxJava / Flow RAC 自定义 + Promise useEffect + 自定义 Hook rxdart
简单 UI 状态 StateFlow / LiveData @Published @State useState ValueNotifier
跨层级状态 ViewModel + StateFlow 单例 + Property @Provide/@Consume Context/Redux Provider/Riverpod

九、共同的最佳实践

  1. 生命周期绑定:订阅要在页面/组件销毁时取消,避免泄漏。
  2. 线程/调度:UI 更新必须在主线程/主 Isolate,注意 observeOn / UIScheduler
  3. 防抖与节流:输入、滚动等高频事件务必做时间控制。
  4. 取消旧请求:搜索、联想场景用 switchMap / flatMap(.latest) 或 useEffect 清理。
  5. 错误处理:onError / catch / retry 统一处理,避免吞掉异常。
  6. 弱引用:闭包中 [weak self] / WeakReference,防止循环引用。

十、总结

维度 共性
本质 观察者模式 + 数据流抽象
思想 声明式、数据驱动、自动传播
操作符 map、filter、combine、flatMap、debounce 等在各平台都有对应
取消 Disposable / 清理函数 / 生命周期绑定
热/冷 部分平台区分热流(共享)与冷流(按需执行)
应用 表单校验、搜索联想、多源合并、列表分页、实时计算

掌握这些共性后,从一个平台迁移到另一个平台时,可以快速找到对应概念和 API,写出一致、可维护的响应式代码。


参考资源

iOS 开发中的 ReactiveCocoa (RAC)

由浅入深,从基本概念到源码解析,带你全面掌握 Swift 响应式编程


一、什么是响应式编程?

1.1 从命令式到声明式

传统的命令式编程中,我们通过直接修改状态和调用方法来驱动程序执行:

1
2
3
4
5
6
7
8
9
// 命令式:监听文本框变化
textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged)

func textDidChange() {
let text = textField.text ?? ""
if text.count >= 3 {
searchUsers(text)
}
}

响应式编程则将数据流视为核心概念,用声明式的方式描述「当数据变化时该做什么」:

1
2
3
4
5
6
7
// 响应式:声明数据流关系
textField.reactive.continuousTextValues
.filter { ($0?.count ?? 0) >= 3 }
.debounce(0.3, on: QueueScheduler.main)
.observeValues { [weak self] text in
self?.searchUsers(text ?? "")
}

1.2 ReactiveCocoa 与 ReactiveSwift

  • ReactiveSwift:纯 Swift 实现的响应式核心库,提供 Signal、SignalProducer 等基础类型
  • ReactiveCocoa:基于 ReactiveSwift,为 Cocoa/Cocoa Touch 提供 UI 绑定、扩展和便捷 API

两者关系可以理解为:ReactiveSwift 是引擎,ReactiveCocoa 是上层封装。

1.3 为什么选择 RAC?

  • 统一抽象:将 delegate、回调、通知、KVO、Target-Action 等统一为「事件流」
  • 可组合:通过 map、filter、combineLatest 等操作符组合数据流
  • 减少状态:用数据流替代分散的中间变量
  • 声明式:代码更贴近「业务意图」,易于阅读和维护

二、核心概念

2.1 事件 (Event)

Event 是事件流中的最小传输单元,类似一次性的直播流中的一帧:

1
2
3
4
5
6
public enum Event<Value, Error: Swift.Error> {
case value(Value) // 携带一个值
case failed(Error) // 失败(携带错误)
case completed // 正常完成
case interrupted // 被中断
}

一个典型的流:若干个 .value,最后以 .completed.failed 结束;.interrupted 表示订阅被取消。

2.2 观察者 (Observer)

Observer 负责向事件流发送事件:

1
2
3
4
5
6
let (signal, observer) = Signal<String, Never>.pipe()

// observer 用于发送事件
observer.send(value: "Hello")
observer.send(value: "World")
observer.sendCompleted()

2.3 可销毁对象 (Disposable)

订阅信号会返回 Disposable,用于取消订阅、释放资源:

1
2
3
4
5
6
let disposable = signalProducer.start { value in
print(value)
}

// 不再需要时取消订阅
disposable.dispose()

2.4 核心类型总览

类型 描述 类比
Signal 热信号,单向事件流,所有者控制发送 直播画面
SignalProducer 冷信号,延迟执行,每次 start 创建新流 点播视频
Property 可观察的「有值」盒子,永不失败 播放进度条
Action 串行执行、可启用/禁用的操作 自动售货机
Lifetime 观察的生命周期,用于自动取消 观看时间段

三、Signal 与 SignalProducer

3.1 Signal:热信号

  • 热信号:无论有没有观察者,事件都会持续发送
  • 所有者完全控制何时发送、发送什么
  • 观察者只能订阅,不能影响流的产生
1
2
3
4
5
6
7
8
9
10
// 创建 Signal 的方式:pipe
let (signal, observer) = Signal<String, Never>.pipe()

signal.observeValues { value in
print("收到: \(value)")
}

observer.send(value: "A") // 输出: 收到: A
observer.send(value: "B") // 输出: 收到: B
observer.sendCompleted()

3.2 SignalProducer:冷信号

  • 冷信号:只有在 start 时才真正执行
  • 每次 start 都会创建新的 Signal,重新跑一遍逻辑
  • 适合网络请求、文件读取等「按需执行」的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let producer = SignalProducer<String, Never> { observer, _ in
print("开始执行") // 每次 start 都会打印
observer.send(value: "Hello")
observer.send(value: "World")
observer.sendCompleted()
}

let d1 = producer.startWithValues { print("观察者1: \($0)") }
// 输出: 开始执行
// 输出: 观察者1: Hello
// 输出: 观察者1: World

let d2 = producer.startWithValues { print("观察者2: \($0)") }
// 再次输出: 开始执行
// 输出: 观察者2: Hello
// 输出: 观察者2: World

3.3 热信号 vs 冷信号

特性 Signal (热) SignalProducer (冷)
执行时机 由发送者决定 start 时执行
多订阅 共享同一流 每个订阅独立执行
典型场景 按钮点击、通知 网络请求、文件读取

四、Property 与 Action

4.1 Property:可观察的盒子

Property 表示「始终有一个当前值」且「永不失败」的流,适合表示 UI 状态、配置等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MutableProperty:可读可写
let username = MutableProperty<String>("")

// 读取当前值
print(username.value)

// 监听变化
username.signal.observeValues { newValue in
print("用户名变为: \(newValue)")
}

// 更新值
username.value = "张三"
username.modify { $0 = "李四" }

4.2 Action:串行操作

Action 将「输入 → 输出」封装为可复用、可启用/禁用的操作,且保证串行执行(同一时间只处理一个请求):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let searchAction = Action<String, [User], NSError> { keyword in
return searchAPI(keyword) // 返回 SignalProducer
}

// 绑定启用条件(例如:输入非空)
let enabledSearch = Action(state: viewModel.keyword, enabledIf: { $0.count > 0 }) { _, keyword in
return searchAPI(keyword)
}

// 执行
searchAction.apply("Swift").startWithResult { result in
switch result {
case .success(let users):
self.users = users
case .failure(let error):
self.showError(error)
}
}

五、常用操作符

5.1 转换类

map:转换每个值

1
2
3
signal
.map { $0.uppercased() }
.observeValues { print($0) }

filter:过滤值

1
2
3
signal
.filter { $0.count >= 3 }
.observeValues { print($0) }

reduce:聚合为单个值(在 completed 时发出)

1
2
3
signal
.reduce(0) { $0 + $1 }
.observeValues { print("总和: \($0)") }

5.2 组合类

combineLatest:合并多个流的最新值

1
2
3
4
let combined = Signal.combineLatest(usernameSignal, passwordSignal)
combined.observeValues { (user, pwd) in
loginButton.isEnabled = user.count > 0 && pwd.count >= 6
}

zip:按顺序一一配对

1
2
let zipped = Signal.zip(numbersSignal, lettersSignal)
// (1,A), (2,B), (3,C)...

5.3 扁平化 (flatten)

merge:内层多个流的值按到达顺序全部输出

concat:按顺序消费内层流,前一个完成后才订阅下一个

latest:只保留「最新」的内层流,常用于搜索联想(新关键词来时取消旧请求)

1
2
3
4
5
6
7
searchKeywordSignal
.flatMap(.latest) { keyword in
searchAPI(keyword) // 新关键词会取消上一次请求
}
.observeValues { users in
self.updateUI(users)
}

5.4 错误处理

1
2
3
4
producer
.flatMapError { _ in SignalProducer(value: []) } // 失败时返回空数组
.retry(upTo: 3) // 失败重试 3 次
.mapError { CustomError.wrapped($0) } // 转换错误类型

六、源码与实现原理

6.1 Signal 的核心结构

Signal 通过闭包保存「如何向观察者推送事件」的逻辑,内部维护观察者列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Signal 的核心:Generator 闭包
// 当有观察者订阅时,闭包被调用,传入 Observer
public init(_ generator: (Observer) -> Disposable?)

// pipe 创建方式
public static func pipe() -> (Signal, Signal.Observer) {
var observer: Signal.Observer!
let signal = Signal { innerObserver in
observer = innerObserver
return nil
}
return (signal, observer)
}

observer.send(value:) 被调用时,所有已注册的观察者都会收到该值。

6.2 SignalProducer 的延迟执行

SignalProducer 保存的是「如何创建 Signal」的闭包,而不是 Signal 本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 每次 start 时:
// 1. 创建一个新的 Signal(通过 pipe 或类似机制)
// 2. 执行 generator 闭包,将 observer 传入
// 3. 闭包内的逻辑开始执行,向 observer 发送事件
public func start(_ observer: Observer) -> Disposable {
let (signal, pipeObserver) = Signal.pipe()
let disposable = CompositeDisposable()
disposable += signal.observe(observer)
disposable += self.startWithSignal { signal, innerDisposable in
pipeObserver.observe(signal)
disposable += innerDisposable
}
return disposable
}

因此每次 start 都会触发一次完整的「创建 + 执行」过程。

6.3 操作符的链式调用

mapfilter 等操作符本质是创建新的 Signal/SignalProducer,内部订阅上游并转换后传给下游:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// map 的简化逻辑
extension Signal {
public func map<U>(_ transform: @escaping (Value) -> U) -> Signal<U, Error> {
return Signal { observer in
return self.observe { event in
switch event {
case let .value(value):
observer.send(value: transform(value))
case .completed:
observer.sendCompleted()
case let .failed(error):
observer.send(error: error)
case .interrupted:
observer.sendInterrupted()
}
}
}
}
}

七、实战示例

7.1 登录表单校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class LoginViewModel {
let username = MutableProperty("")
let password = MutableProperty("")

var canLogin: Property<Bool> {
return Property(
initial: false,
then: Signal.combineLatest(username.signal, password.signal)
.map { user, pwd in
user.count >= 3 && pwd.count >= 6
}
)
}

let loginAction: Action<(String, String), User, Error>

init() {
loginAction = Action(enabledIf: canLogin) { [weak self] _, input in
return loginAPI(username: input.0, password: input.1)
}
}
}

// ViewController 中绑定
viewModel.canLogin.signal
.observeValues { [weak loginButton] canLogin in
loginButton?.isEnabled = canLogin
}

loginButton.reactive.pressed = CocoaAction(viewModel.loginAction) { _ in
(viewModel.username.value, viewModel.password.value)
}

7.2 搜索联想(防抖 + 取消旧请求)

1
2
3
4
5
6
7
8
9
10
11
12
13
searchTextField.reactive.continuousTextValues
.filter { ($0 ?? "").count >= 2 }
.debounce(0.3, on: QueueScheduler.main)
.flatMap(.latest) { [weak self] keyword -> SignalProducer<[User], Error> in
guard let keyword = keyword, !keyword.isEmpty else {
return .init(value: [])
}
return self?.searchAPI(keyword) ?? .empty
}
.observe(on: UIScheduler())
.startWithValues { [weak self] users in
self?.updateSearchResults(users)
}

7.3 多数据源合并展示

1
2
3
4
5
6
7
8
9
10
11
12
13
let localUsers = loadLocalUsers()   // SignalProducer<[User], Never>
let remoteUsers = fetchRemoteUsers() // SignalProducer<[User], Error>

SignalProducer.combineLatest(
localUsers.flatMapError { _ in .init(value: []) },
remoteUsers.flatMapError { _ in .init(value: []) }
)
.map { local, remote in
mergeAndDeduplicate(local: local, remote: remote)
}
.startWithValues { [weak self] users in
self?.tableView.reload(with: users)
}

八、ReactiveCocoa 的 Cocoa 扩展

ReactiveCocoa 为 UIKit 提供了 reactive 命名空间下的扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
// UIControl 事件
button.reactive.controlEvents(.touchUpInside)
.observeValues { _ in print("点击") }

// UITextField 文本
textField.reactive.continuousTextValues
.observeValues { print($0 ?? "") }

// 双向绑定(需要导入 ReactiveCocoa)
label.reactive.text <~ viewModel.title // 单向:ViewModel -> Label

// CocoaAction 绑定按钮
button.reactive.pressed = CocoaAction(viewModel.submitAction)

九、最佳实践与注意事项

  1. 避免循环引用:在闭包中使用 [weak self],在适当时机 dispose 订阅
  2. 主线程更新 UI:使用 .observe(on: UIScheduler()) 确保 UI 更新在主线程
  3. 合理选择热/冷信号:异步任务、网络请求用 SignalProducer;UI 事件、通知用 Signal
  4. 错误类型:尽量使用 Never 表示不会失败的流,便于组合
  5. 利用 Lifetime:在 ViewController/View 销毁时通过 Lifetime 自动取消订阅

十、总结

概念 要点
Event 事件流的传输单元:value / failed / completed / interrupted
Signal 热信号,由所有者控制,多订阅共享
SignalProducer 冷信号,延迟执行,每次 start 独立运行
Property 有当前值、不失败的流,适合状态
Action 串行、可启用/禁用的操作封装
操作符 map、filter、combineLatest、flatMap 等组合与转换流
实践 表单校验、搜索联想、多源合并、UI 绑定

掌握这些概念后,你就能用响应式思维重构业务逻辑,写出更清晰、更易维护的 Swift/iOS 代码。


参考资源