Skip to Content
Course content

87: Subjects: PassthroughSubject and CurrentValueSubject

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once worked with a developer who was building a real-time stock ticker app. He had everything wired up with Combine, but he hit a weird bug: when a user navigated to a specific stock's detail page, the price label would stay empty until the next price update arrived from the server, which could take thirty seconds. He was using a PassthroughSubject to broadcast price changes. The problem was that the detail view subscribed to the subject after the latest price had already been emitted. Since PassthroughSubject doesn't store anything, the new subscriber just sat there in the dark, waiting for an event that had already passed.

This is the fundamental divide between the two types of Subjects in Combine. While both allow you to manually "inject" values into a stream using the send() method, they behave very differently regarding memory and timing.

The Doorbell Approach: PassthroughSubject

Think of a PassthroughSubject like a doorbell. When someone rings it, the sound happens, and anyone listening at that exact moment hears it. If you step out of the house for a minute, you miss the ring entirely. There is no "recording" of the bell ringing; it's a pure event.

In your code, you use this for events that don't represent a persistent state. For example, a "Refresh" button tap or a "UserDidLogout" notification. You don't care if a subscriber missed the logout event from three minutes ago; you only care about the one happening now.

let notificationCenter = PassthroughSubject<String, Never>()

// Subscriber A joins early
notificationCenter.sink { print("Subscriber A received: \($0)") }

notificationCenter.send("Update 1") // Both hear it if B was here, but only A hears this.

// Subscriber B joins late
notificationCenter.sink { print("Subscriber B received: \($0)") }

notificationCenter.send("Update 2") 
// Both A and B print "Update 2"

Notice that Subscriber B never saw "Update 1". It's gone forever. I usually reach for PassthroughSubject when I'm implementing a coordinator pattern or handling one-off triggers in a ViewModel.

The Light Switch Approach: CurrentValueSubject

Now, imagine a light switch. You don't need to be standing in the room when the switch is flipped to know whether the light is currently on or off. You can just walk in and look at the switch. That is exactly how CurrentValueSubject works.

A CurrentValueSubject requires an initial value and it remembers the most recent value it has emitted. When a new subscriber attaches, the subject immediately sends that current value to them. This solves the "blank label" problem my colleague had with the stock ticker.

let connectionStatus = CurrentValueSubject<String, Never>("Disconnected")

// Subscriber A joins
connectionStatus.sink { print("Subscriber A sees: \($0)") } 
// Prints immediately: "Subscriber A sees: Disconnected"

connectionStatus.send("Connected")

// Subscriber B joins late
connectionStatus.sink { print("Subscriber B sees: \($0)") } 
// Prints immediately: "Subscriber B sees: Connected"

One killer feature of CurrentValueSubject is that you can access the current state synchronously without a subscription. You can just call connectionStatus.value. This is incredibly useful when you need to check a state inside an if statement or a guard clause without setting up a whole pipeline. Just be careful not to rely on `.value` too heavily, or you're basically just using a fancy wrapper around a variable and losing the benefits of reactive programming.




📋 Practical Task

Exercise: Building a Connection Status Monitor

Create a NetworkMonitor class that simulates a network manager. Your task is to implement two different types of communication using Subjects:

  • Connection State: Use a CurrentValueSubject to track whether the app is online or offline. This should start as offline.
  • Retry Trigger: Use a PassthroughSubject to signal when a user has manually tapped a "Retry" button.

To complete the exercise, write a small script that:

  1. Subscribes to the connection state and prints the current status.
  2. Subscribes to the retry trigger and prints "Attempting to reconnect..." whenever a signal is sent.
  3. Changes the connection state to online.
  4. Sends a retry signal.
  5. Creates a second subscriber to the connection state and verifies that it immediately receives the online status without having to wait for a new change.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.