Combine 订阅机制完整解析(Publisher / Subscriber / Subscription)

3 阅读4分钟

一、核心结论

Combine 是一个受控的数据流系统(pull-controlled push system)

  • 数据流方向:Publisher → Subscriber
  • 控制流方向:Subscriber → Publisher(通过 Subscription.request)

想象一个报社 → 邮递员 → 订阅者的模型:

  • 报社(Publisher) — 生产报纸
  • 你(Subscriber) — 消费报纸
  • 订阅合同(Subscription) — 控制"送几份、什么时候送、能不能取消"

数据流(报纸的流向):

报社 → 邮递员 → 你家门口

控制流(谁说了算):

你 → 合同 → 报社

关键点来了 —— 不是报社想送多少就送多少,而是你决定要几份

这就是所谓的 "受控的数据流" ,英文叫 pull-controlled push system,意思是:

  • 数据是被推送过来的(push)
  • 但推多少由消费者拉取控制(pull-controlled)

三大核心角色:

角色职责
Publisher生产数据
Subscriber消费数据
Subscription控制数据流(背压 + 生命周期)

Subscription 是怎么产生的?

还是用报社比喻:

你去报社签合同的过程就是 subscribe,签完合同那一刻,合同(Subscription)就产生了。

你(Subscriber)去找报社(Publisher)说"我要订阅"
         ↓
报社创建一份合同(Subscription)
         ↓
报社把合同交给你:publisher.receive(subscription:)
         ↓
你拿到合同,告诉报社"先给我发3份":subscription.request(.max(3))
         ↓
报社开始送报纸
import Combine

// ============================================
// 第一步:自定义一个 Subscriber
// ============================================
class MySubscriber: Subscriber {
    
    // 告诉 Publisher:我接收的数据类型是 Int,错误类型是 Never
    typealias Input = Int
    typealias Failure = Never
    
    // 保存合同,不然合同会被释放,数据就停了
    var subscription: Subscription?
    
    // ✅ 第四步:publisher 把合同交给我
    func receive(subscription: Subscription) {
        print("📋 收到合同了")
        self.subscription = subscription
        
        // ✅ 第五步:我告诉 publisher,先给我发 2 个
        subscription.request(.max(2))
        print("🙋 我要了 2 个数据")
    }
    
    // ✅ 第六步:数据流过来了
    func receive(_ input: Int) -> Subscribers.Demand {
        print("📦 收到数据:\(input)")
        
        // 返回值表示:收到这个之后,再追加要几个
        // .none 表示不追加,维持之前的配额
        return .none
    }
    
    // ✅ 最后:流结束了
    func receive(completion: Subscribers.Completion<Never>) {
        print("✅ 数据流结束:\(completion)")
    }
}

// ============================================
// 第二步:创建 Publisher
// ============================================
let publisher = [1, 2, 3, 4, 5].publisher

// ============================================
// 第三步:创建 Subscriber
// ============================================
let subscriber = MySubscriber()

// ============================================
// 触发整个流程!
// ============================================
publisher.subscribe(subscriber)  // ← 就这一行启动了所有事情

输出结果

📋 收到合同了
🙋 我要了 2 个数据
📦 收到数据:1
📦 收到数据:2
✅ 数据流结束:finished

注意:虽然 publisher 有 5 个数据,但我们只要了 2 个,所以只收到 1 和 2。但因为这是数组 publisher,它发完你要的之后就结束了,所以还是触发了 completion。


对应流程图

publisher.subscribe(subscriber)
         ↓
publisher 内部 new Subscription()         ← Combine 帮你做的
         ↓
subscriber.receive(subscription:)         ← 你的代码:收到合同
         ↓
subscription.request(.max(2))             ← 你的代码:要2个
         ↓
subscriber.receive(1)                     ← 你的代码:收到数据
subscriber.receive(2)                     ← 你的代码:收到数据
         ↓
subscriber.receive(completion: .finished) ← 你的代码:结束

二、核心协议定义

Publisher

protocol Publisher {
    associatedtype Output
    associatedtype Failure: Error
    func receive<S: Subscriber>(subscriber: S)
}

要点:

  • 不直接发数据
  • 必须先建立订阅

Subscriber

protocol Subscriber {
    associatedtype Input
    associatedtype Failure: Error

    func receive(subscription: Subscription)
    func receive(_ input: Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Failure>)
}

要点:

  • 控制数据流(demand)
  • 接收 value 和 completion

Subscription

protocol Subscription {
    func request(_ demand: Subscribers.Demand)
    func cancel()
}

要点:

  • Publisher 创建
  • Subscriber 使用
  • 控制流量 + 生命周期

三、完整执行流程(从 sink 开始)

代码:

publisher
    .print()
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })

执行顺序:

1. sink 被调用

  • 创建一个 Subscriber(Sink)

2. 建立订阅

publisher.receive(subscriber: subscriber)

3. Publisher 响应订阅

  • 创建 Subscription
  • 调用:
subscriber.receive(subscription: subscription)

4. Subscriber 请求数据

subscription.request(.unlimited)

说明:

  • sink 默认策略:无限需求

5. 数据流动条件成立

必须满足:

  • 已建立 subscription
  • 已 request demand

否则不会发数据


6. Publisher 发送数据

publisher.send(1)

7. 数据传递路径

Publisher → Subscription → Subscriber.receive(value)

8. 完成事件

Publisher → Subscriber.receive(completion)

四、subscribe 的本质

subscribe(receive(subscriber:))做三件事:

  1. 建立 Publisher 和 Subscriber 连接
  2. 创建 Subscription
  3. 把 Subscription 交给 Subscriber

结论:

  • subscribe 只负责“连接”
  • 不负责“发送数据”

五、Subscription 的本质

Subscription 是数据流控制器

职责:

  • 记录 demand
  • 控制是否允许发送
  • 处理 cancel

关键点:

  • 不主动发送数据
  • 只是控制通道

六、Demand(背压机制)

定义:

enum Subscribers.Demand {
    case unlimited
    case max(Int)
}

含义:

  • Subscriber 告诉 Publisher:我能处理多少

规则:

  • Publisher 不能超过 demand 发送数据

七、数据流 vs 控制流

数据流:

Publisher → Subscriber

控制流:

Subscriber → Subscription → Publisher

总结:

  • Subscriber 决定“要多少”
  • Publisher 决定“怎么发”
  • Subscription 执行控制

八、不同 Publisher 行为差异

自动发送:

  • Empty:立即 finished
  • Just:一个值 + finished
  • CurrentValueSubject:先发当前值

手动发送:

  • PassthroughSubject:必须 send()

九、PassthroughSubject 为什么 sink 没输出

let publisher = PassthroughSubject<Int, Never>()
publisher.sink { ... }

原因:

  • 已订阅 ✔
  • 已 request ✔
  • 没有事件 ❗

只有:

publisher.send(1)

才会触发输出


十、.print() 的作用

.print() 只是调试工具,会打印:

  • receive subscription
  • request
  • value
  • completion

注意:

  • 不影响逻辑
  • 只是观察行为

十一、常见误区

误区 1:

  • sink 只是触发订阅 ❌
  • sink = 创建 Subscriber + 订阅 + request ✔

误区 2:

  • Subscription 是 Subscriber 创建 ❌
  • Subscription 是 Publisher 创建 ✔

误区 3:

  • Subscription 发送数据 ❌
  • Publisher 决定发送 ✔

误区 4:

  • subscribe 会触发数据 ❌
  • request 才决定是否能发 ✔

十二、完整模型

1. sink 创建 Subscriber
2. publisher.receive(subscriber)
3. Publisher 创建 Subscription
4. subscriber.receive(subscription)
5. subscriber.request(demand)
6. Publisher 根据 demand 决定发送
7. Subscription 控制转发
8. subscriber.receive(value)

十三、一句话理解

Combine = 一个由 Subscription 控制、由 Subscriber 驱动、由 Publisher 提供数据的流系统