11.9 · Which thread each stage runs on, schedulers, throttle versus debounce, and timers

Combine moves values between threads and over time. These questions make that visible: where each stage executes, what “immediate” really means, and which time-based operator you want.

Mental model. receive(on:) changes where everything after it runs; subscribe(on:) changes where subscription and upstream work happen. Time operators need a scheduler that is actually running.

Threads and schedulers

subscribe(on:) versus receive(on:), and immediate versus queue-backed delivery.

  1. Which thread does each stage of a pipeline run on, and what do subscribe(on:) and receive(on:) each move? 11.9.1 · L3
    By default a stage runs on whatever thread the value arrives on: the publisher decides where it emits. receive(on:) is a downstream switch: from that point on…
  2. What is the difference between receive(on: ImmediateScheduler) and receive(on: DispatchQueue.main) for the order of code? 11.9.2 · L3
    ImmediateScheduler.shared runs the work synchronously, right now, on the current thread: the two values arrive before the next line runs (“immediate 1…

Operators over time

Debounce, throttle with latest or first, and timers on the run loop.

  1. What do debounce, throttle(latest: true) and throttle(latest: false) each emit for a burst of events, and which do you use for search, scrolling and taps? 11.9.3 · L3
    Take five values sent 40 ms apart. debounce(for: 150 ms) waits for a quiet gap and emits only the last value of the burst, after the gap: [5]. It never emits…
  2. Why does Timer.publish emit nothing until the run loop runs, and which run-loop mode should it use? 11.9.4 · L2
    Timer.publish(every:on:in:) schedules a real Timer on a run loop in a mode, and timers are only fired by a running run loop. In the check the subscription is…

More in Combine