Combine - Deferred

353 阅读3分钟

Swift Combine 中的 *Deferred* 是一种延迟创建 Publisher 的机制,它允许在订阅发生时才生成实际的 Publisher 实例。这一特性在处理需要动态生成数据源或避免资源提前占用时非常有用‌

基本概念

  1. 延迟初始化
    Deferred 将 Publisher 的创建推迟到订阅发生时

    • 在声明 Deferred 时,它不会立即执行内部的闭包,而是等到有订阅者订阅后才生成实际的 Publisher。
    • 这种机制类似于“惰性初始化”,避免在不需要时占用资源。
  2. 动态生成 Publisher
    每次订阅时,Deferred 会重新执行其闭包,生成全新的 Publisher 实例

    • 适用于需要根据订阅时的状态(如时间、用户配置、环境变量等)动态调整数据源的场景。
  3. 避免副作用提前触发

    • 如果直接在 Publisher 的初始化阶段执行某些操作(如网络请求),使用 Deferred 可以确保这些操作在订阅后才执行,避免不必要的副作用。

核心行为

let deferredPublisher = Deferred {
    // 这个闭包在订阅时才会执行
    return Just(Int.random(in: 1...100))
}

// 订阅1:生成新的随机数
deferredPublisher.sink { print("Sub1: ($0)") } // 输出如 Sub1: 42

// 订阅2:再次执行闭包,生成另一个随机数
deferredPublisher.sink { print("Sub2: ($0)") } // 输出如 Sub2: 87
  • 每次订阅都会触发闭包,生成独立的 Publisher。
  • 直接使用 Just 或 Future 时,值或操作可能在声明时就被固定,而 Deferred 确保动态性。

典型使用场景

  1. 依赖订阅时的状态
Deferred {
    let currentTime = Date()
    return Just(currentTime) // 每次订阅生成当前时间
}
  1. 避免提前触发副作用
Deferred {
    // 订阅后才发起网络请求
    return URLSession.dataTaskPublisher(for: url)
}
  1. 为每个订阅提供独立流
Deferred {
    // 每次订阅生成新的计时器
    return Timer.publish(every: 1, on: .main, in: .default)
}

核心特性解析

  1. 延迟初始化
    Deferred 包装的 Publisher 不会在声明时立即创建,而是在首次被订阅时才会初始化内部的 Publisher。这确保了资源的按需分配,避免不必要的开销‌。
  2. 动态数据源支持
    每次订阅 Deferred 时,其闭包会重新执行并生成新的 Publisher。这使得它可以基于订阅时的最新状态(如当前时间、用户配置等)生成数据流‌。
  3. 与订阅者交互的时机
    当 Deferred 接收到订阅请求时,才会触发内部 Publisher 的创建和值的分发。这一机制与 Combine 的“按需请求值”原则一致‌。

典型使用场景

  • 依赖外部状态的异步操作
    例如,在订阅时根据当前网络状态生成不同的 URL 请求:
let deferredPublisher = Deferred { 
    let currentEndpoint = NetworkManager.currentEndpoint 
    return URLSession.shared.dataTaskPublisher(for: currentEndpoint) 
}
  • 避免副作用提前触发
    若直接使用 Future 或 Just 可能在初始化时就执行闭包,而 Deferred 确保闭包仅在订阅时执行‌。
  • 多次订阅时重置状态
    每次订阅都会重新生成内部 Publisher,适用于需要独立数据流的场景(如分页加载)‌

与普通 Publisher 的对比

类型初始化时机数据生成逻辑适用场景
*Just*声明时立即创建固定值(一次性发送)静态数据
*Future*声明时立即执行闭包异步操作(完成后发送结果)单次异步任务
*Deferred*订阅时创建动态生成(每次订阅重新执行闭包)动态依赖、避免副作用

注意事项

  • 闭包中的副作用:确保 Deferred 的闭包无额外副作用,或副作用符合预期(如多次订阅可能多次触发闭包)。
  • 内存管理:若闭包捕获外部变量,需注意循环引用问题,可通过 [weak self] 或 [unowned] 避免‌。

通过 Deferred,开发者可以更灵活地控制数据流的生成时机,优化资源利用并适配动态场景‌。