When assign looks obvious in UI binding

assign looks like the obvious replacement for Rx bind, but in UI code it is often not the safest one.

UI binding is rarely just about moving a value from one stream into one property.

This is where Combine UI binding in UIKit gets risky when the migration optimizes only for shorter syntax.

UI binding is rarely just about one property

It is also about:

  • scheduler choice
  • object lifetime
  • reuse
  • making sure updates do not keep targeting UI that should already be gone

Controllers, cells, and reusable views

A stream may outlive the visible UI.

A reused cell may still receive updates meant for its previous content.

A label or button may disappear while the upstream publisher keeps emitting.

receive(on:) plus sink is often better

It makes the important parts explicit.

  • You decide where delivery happens.
  • You choose weak capture when the target should not be retained.
  • You can see subscription lifetime clearly instead of hiding it behind a very short binding expression.

assign is still valid

When the target is stable and strongly owned for the full subscription lifetime, it can still be valid.

But that should be a deliberate decision, not the default migration reflex.

Simplified pseudocode

This is simplified pseudocode, not production code.

import Combine
import UIKit

final class ProfileViewController: UIViewController {
    private var cancellables = Set<AnyCancellable>()
    private let nameLabel = UILabel()

    func bind(namePublisher: AnyPublisher<String, Never>) {
        namePublisher
            .receive(on: RunLoop.main)
            .sink { [weak self] name in
                self?.nameLabel.text = name
            }
            .store(in: &cancellables)
    }
}

This keeps the scheduler explicit, uses weak capture, and makes the subscription lifetime visible.

That is usually safer for UI than hiding everything behind one short assign.

If lifecycle matters, explicit beats short

Rx bind carried ergonomic safety that developers often took for granted.

In Combine, you usually have to rebuild that safety manually.

For UI binding, that is usually the right tradeoff.

If your team is also migrating Driver, read Driver Was a Contract, Not Just a UI Stream because these two mistakes often show up together.