iOS MVVM架构中RxSwift的DisposeBag能否置于ViewModel?
Hey there! Let's break this down clearly since you're new to RxSwift and navigating the tricky DisposeBag placement question—totally get why this feels confusing at first.
First: Is the "DisposeBag can only be in ViewController" claim correct?
No, that's an overly rigid rule. There's no RxSwift rule that forbids putting a DisposeBag in a ViewModel. In fact, it's extremely common in MVVM+RxSwift setups to give ViewModels their own DisposeBags, especially when the ViewModel needs to manage its own Observable subscriptions (like your scenario of handling local list edits).
The confusion often comes from worrying about lifecycle mismatches, but as long as your ViewModel's lifecycle is tied to its ViewController (i.e., the ViewController holds a strong reference to the ViewModel, and the ViewModel doesn't outlive the ViewController), a ViewModel-level DisposeBag works perfectly.
How to implement your scenario (ViewModel-side logic with DisposeBag)
Let's walk through a practical example tailored to your use case—managing a list of items with independent selection states, with business logic in the ViewModel.
First, a quick note: Variable is deprecated in modern RxSwift. We'll use BehaviorRelay instead (it's safer, as external code can't modify its value directly—only via accept()).
Step 1: Build your ViewModel with a DisposeBag
import RxSwift import RxCocoa class ItemsViewModel { // Holds your list of items (replace Item with your model type) let itemsRelay = BehaviorRelay<[Item]>(value: []) // ViewModel's own DisposeBag to manage internal subscriptions private let disposeBag = DisposeBag() // Accepts the item selection signal from the ViewController func bindItemSelection(_ selectionSignal: Observable<IndexPath>) { selectionSignal .subscribe(onNext: { [weak self] indexPath in guard let self = self else { return } // Get current items, toggle selection state var updatedItems = self.itemsRelay.value updatedItems[indexPath.row].isSelected.toggle() // Push the updated list back to the relay self.itemsRelay.accept(updatedItems) }) .disposed(by: disposeBag) // Tie this subscription to the ViewModel's bag } // Example: Fetch initial data (could be network/local) func loadInitialItems() { // Mock data—replace with your actual data fetch logic let mockItems = [ Item(name: "First Item", isSelected: false), Item(name: "Second Item", isSelected: false), Item(name: "Third Item", isSelected: false) ] itemsRelay.accept(mockItems) } } // Your item model with selection state struct Item { let name: String var isSelected: Bool }
Step 2: Connect ViewController to ViewModel
import UIKit import RxSwift import RxCocoa class ItemsViewController: UIViewController { @IBOutlet weak var tableView: UITableView! private let viewModel = ItemsViewModel() private let vcDisposeBag = DisposeBag() // For VC-specific bindings override func viewDidLoad() { super.viewDidLoad() setupBindings() viewModel.loadInitialItems() } private func setupBindings() { // 1. Pass tableView selection events to the ViewModel tableView.rx.itemSelected .subscribe(onNext: { [weak self] indexPath in self?.viewModel.bindItemSelection(Observable.just(indexPath)) }) .disposed(by: vcDisposeBag) // 2. Bind ViewModel's items to the tableView viewModel.itemsRelay .bind(to: tableView.rx.items(cellIdentifier: "ItemCell", cellType: ItemCell.self)) { index, item, cell in cell.titleLabel.text = item.name cell.accessoryType = item.isSelected ? .checkmark : .none } .disposed(by: vcDisposeBag) } } class ItemCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel! }
Key things to remember
- Avoid strong reference cycles: Always use
[weak self]in your ViewModel's subscription closures to prevent it from holding onto itself or the ViewController unnecessarily. - Lifecycle alignment: As long as your ViewController holds a strong reference to the ViewModel (and the ViewModel isn't reused across multiple VCs), the ViewModel's
DisposeBagwill be deallocated when the ViewController is destroyed, automatically disposing all its subscriptions. - Use
BehaviorRelayoverVariable:Variablewas deprecated for good reason—BehaviorRelayenforces immutability from outside the ViewModel, making your code safer and more predictable.
Final takeaway
You absolutely can (and should) put a DisposeBag in your ViewModel when it needs to manage its own Observable subscriptions. The "only in ViewController" myth comes from edge cases where ViewModels have longer lifecycles, but in your standard one-to-one VC-ViewModel setup, it's the right approach to keep business logic out of the ViewController.
内容的提问来源于stack exchange,提问作者wsli

