Driver was a bundle of UI guarantees

A lot of RxSwift migrations underestimate Driver because it looked like a convenient type for UI, but it was really a bundle of guarantees.

This is one of the easiest ways for RxSwift to Combine migration bugs to hide in UI code.

Driver meant:

  • main-thread delivery
  • no failure in the UI layer
  • shared side effects
  • replay of the latest value to new subscribers

That combination is why it felt safe in view models and screen bindings. Teams relied on those guarantees even when they never wrote them down.

Combine does not give you that contract in one type

  • receive(on:) only handles delivery.
  • It does not remove failures.
  • It does not replay the current value.
  • It does not automatically share upstream work.
  • It does not stop multiple subscribers from triggering duplicate side effects.

Why naive migrations break behavior

  • A screen that used to show the latest state immediately may now wait for the next emission.
  • A publisher that used to do one network-backed transformation may now do it twice because two subscribers attached.
  • A stream that could never fail into the UI may now need explicit error handling.

The practical migration rule

Stop asking what replaces Driver, and start asking which Driver guarantees this stream actually depended on.

Sometimes the answer is:

  • only main-thread delivery
  • replay
  • sharing
  • all of it

Simplified pseudocode

This is simplified pseudocode, not production code.

import Combine

final class ProfileViewModel {
    private let state = CurrentValueSubject<String, Never>("Loading")

    var titlePublisher: AnyPublisher<String, Never> {
        state
            .receive(on: RunLoop.main)
            .eraseToAnyPublisher()
    }

    func didLoad(name: String) {
        state.send(name)
    }
}

This sample keeps three guarantees visible:

  • Never means the UI-facing stream does not fail
  • CurrentValueSubject replays the latest value
  • receive(on:) keeps delivery on the main thread

If the original Driver also depended on shared upstream work, that part still needs to be preserved separately.

Preserving every guarantee intentionally

A successful migration is not when Driver disappears.

It is when every guarantee it used to provide is either preserved intentionally or removed intentionally.

If your UI binding layer also became shorter but less explicit during the migration, pair this note with Why sink Is Often Safer Than assign for UI Binding.