RxSwift中.never()操作符非测试场景用法及替代方案问询
.never() in RxSwift (Non-Test Scenarios) First, let's recap: .never() creates an Observable sequence that never emits any elements and never terminates. It's easy to dismiss it as only useful for tests, but it has practical applications in production code—especially in patterns like MVVM-C, which you're learning. Let's break down real-world use cases with examples, their necessity, and alternatives.
1. Default Placeholder for Non-Optional Observables
In MVVM-C ViewModels, you often have output Observables that don't have an initial value until some action (like a network call) is triggered. Using .never() lets you keep these Observables non-optional, avoiding extra optional handling in the View layer.
Example Code:
import RxSwift import RxCocoa struct User { let id: String let name: String } class UserProfileViewModel { // Non-optional Observable, initialized with .never() as a placeholder var userInfo: Observable<User> = .never() private let disposeBag = DisposeBag() func fetchUser(for userId: String) { // Simulate a network request with delay let networkRequest = Observable.just(User(id: userId, name: "John Doe")) .delay(.seconds(1), scheduler: MainScheduler.instance) // Replace the placeholder with the actual data sequence userInfo = networkRequest } } // In the ViewController: class UserProfileViewController: UIViewController { private let viewModel = UserProfileViewModel() private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() // Subscribe to userInfo—no need to handle nil! viewModel.userInfo .subscribe(onNext: { [weak self] user in self?.updateUI(with: user) }) .disposed(by: disposeBag) viewModel.fetchUser(for: "123") } private func updateUI(with user: User) { // Update labels, avatar, etc. } }
Why .never() is Necessary Here:
- It keeps
userInfonon-optional, so the View doesn't have to deal withnilchecks or optional binding that adds unnecessary complexity. - Unlike
.empty()(which emits acompletedevent immediately),.never()stays alive until we replace it with the actual sequence. This prevents the View from receiving an unexpected termination signal before data loads.
Alternative:
You could use an optional Observable (var userInfo: Observable<User>?), but that forces the View layer to handle optional subscriptions. .empty() is another option, but you'd need to handle the completed event to avoid the subscription ending prematurely.
2. Manual Lifecycle Control in Coordinators
In MVVM-C, Coordinators manage navigation flow. Sometimes you need a sequence that stays active until a user action (like tapping "Back") triggers termination. .never() acts as a "placeholder" finish signal that only gets replaced when the user initiates a navigation action.
Example Code:
protocol Coordinator { func start() -> Observable<Void> } class ProfileCoordinator: Coordinator { private let navigationController: UINavigationController private let disposeBag = DisposeBag() init(navigationController: UINavigationController) { self.navigationController = navigationController } func start() -> Observable<Void> { let viewModel = UserProfileViewModel() let viewController = UserProfileViewController(viewModel: viewModel) // Initialize finish signal with .never()—it won't terminate until we say so var finishSignal: Observable<Void> = .never() // Bind back button tap to terminate the coordinator's sequence finishSignal = viewController.backButton.rx.tap .do(onNext: { [weak self] in self?.navigationController.popViewController(animated: true) }) navigationController.pushViewController(viewController, animated: true) return finishSignal } }
Why .never() is Necessary Here:
- It ensures the Coordinator's
start()method returns a sequence that doesn't terminate automatically, aligning with the Coordinator pattern's goal of letting the user control navigation flow. - Using
.never()avoids having to manage aPublishSubjectorBehaviorSubject(which require manual disposal and event emission), keeping the code cleaner and less error-prone.
Alternative:
You could use a PublishSubject<Void>(), but you'd need to remember to send a next event when the coordinator finishes, and dispose of the subject properly. .never() eliminates this boilerplate entirely.
3. Temporary Placeholder for Unimplemented Features
During development, you might have Observables tied to features that aren't ready yet. .never() lets you keep your code compiling without triggering unintended events (like empty states or errors).
Example Code:
enum Theme { case light, dark } class SettingsViewModel { let themeChanged: Observable<Theme> init() { #if DEBUG // Theme switching isn't implemented yet—use .never() to avoid breaking the UI themeChanged = .never() #else // Production: Use the actual theme change sequence themeChanged = ThemeManager.shared.themeChanged #endif } }
Why .never() is Necessary Here:
- It prevents the View from receiving unexpected events (like a
completedsignal from.empty()) that could break UI logic before the feature is ready. - It keeps your codebase consistent—you don't have to comment out or modify Observable declarations as you iterate on features.
Alternative:
You could use .empty(), but that would trigger a completed event, which might cause the View to stop listening for future theme changes once the feature is implemented. .never() avoids this issue entirely.
Key Takeaways
.never() shines in scenarios where you need:
- A non-optional, inert Observable placeholder
- An infinitely alive sequence that only terminates on manual trigger
- A safe stand-in for unimplemented features without disrupting existing logic
内容的提问来源于stack exchange,提问作者Nikita Vlasenko

