Removing rx.tap cleanly

Removing rx.tap does not mean going back to scattered target-action code. It means being more deliberate about where event wiring lives.

In practice, this is a UIKit event-handling decision, not just a Combine syntax decision.

UIAction is usually the cleanest replacement

For simple UIKit actions, UIAction is usually the cleanest replacement.

If a button only:

  • dismisses a screen
  • saves a form
  • opens a picker
  • triggers one local method

adding a full publisher chain does not improve the code. It just changes the syntax.

Keep action setup in one place

If button behavior is configured together during setup, the code stays readable.

If event wiring gets spread across lifecycle methods, helper extensions, and random sink chains, the migration got harder, not better.

When taps are part of real stream logic

CombineCocoa is useful when taps are part of real stream logic.

If the event is:

  • merged with other publishers
  • throttled
  • transformed
  • coordinated with async work

a publisher is a good fit. If not, native UIKit is often enough.

The practical rule

  • Use UIAction for local UI actions.
  • Use CombineCocoa when the tap genuinely participates in a Combine pipeline.

Simplified pseudocode

This is simplified pseudocode, not production code.

import UIKit

final class SettingsViewController: UIViewController {
    private let saveButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()

        saveButton.addAction(UIAction { [weak self] _ in
            self?.save()
        }, for: .touchUpInside)
    }

    private func save() {
        // Save settings.
    }
}

This is a local UI action, so UIAction is enough.

There is no need to rebuild a full reactive chain if the tap only calls one method.

When the intent is clearer than before

The mistake is assuming every Rx event must stay reactive after the migration.

That is how teams end up rebuilding rx.tap everywhere, even when the original behavior was just “call one method on tap.”

If the bigger migration question is still “what exactly must survive from RxSwift?”, go back to the main rule in the lead migration note.