You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 DisposeBag will be deallocated when the ViewController is destroyed, automatically disposing all its subscriptions.
  • Use BehaviorRelay over Variable: Variable was deprecated for good reason—BehaviorRelay enforces 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:12:15