Good Practice with RxSwift & MVVM - Exposing Variables

Just a quick tip for the use of RxSwift within an MVVM architected application. Most people likely know to do this, but it never hurts to throw it out there.

In a view model, my instinct was to set it up like so:

public protocol PeopleViewModel {
    /// Drives a collection of person summaries (UITableView or UICollectionView for example).
    var personViewModels: Variable<[PersonViewModel]> { get }
    /// Whether or not the perople are people loaded.
    var isLoading: Variable<Bool>
    /// Whether there are any people.
    var isEmpty: Variable<Bool>
    /// Fetches people after a given index (useful for paginated responses).
    func fetchPeople(after index: Int)

Within the view, I would do the usual stuff:

private func configure(with viewModel: PeopleViewModel) {
        .bind(to: tableView.rx.items(cellIdentifier: cellIdentifier, cellType: PersonTableViewCell.self)) { (row: Int, cellModel: UserSummaryViewModeling, cell: UserTableViewCell) in
            cell.viewModel = cellModel
            viewModel.fetchPeople(after: row)
        .disposed(by: disposeBag)

    /// etc…

However, this was poor enforcement of access control and from within the view, if I was evil, I could do something like this:

viewModel.personViewModels.value = []

All my people are gone!!!

What I really want is:

public protocol PeopleViewModel {
    var personViewModels: Driver<[PersonViewModel]> { get }
    var isLoading: Driver<Bool>
    var isEmpty: Driver<Bool>
    func fetchPeople(after index: Int)

The implementation of which would look like:

final class PeopleViewModelImp {
    let personViewModels: Driver<[PersonViewModel]> { get }
    let isLoading: Driver<Bool>
    let isEmpty: Driver<Bool>

    fileprivate let items = Variable([PersonViewModel]())
    fileprivate let isEmptyVariable = Variable(false)
    fileprivate let isLoadingVariable = Variable(false)
    fileptivate let apiClient: APIClient
    fileprivate let disposeBag: DisposeBag

    init(apiClient: APIClient) {
        personViewModels = items.asDriver()
        isEmpty = isEmptyVariable.asDriver()
        isLoading = isLoadingVariable.asDriver()
        self.apiClient = apiClient
        fetchPeople(after: 0)

    func fetchPeople(after index: Int) {
        isLoadingVariable.value = true

        apiClient.fetchPeople().subscribe(onNext: { people in
            let viewModels =
            items.value += viewModels
            isEmptyVariable.value = items.value.isEmpty
            isLoadingVariable.value = false
        .disposed(by: disposeBag)


Now, in an ideal world, the fetching of the people would not be disposed of within the actual view model. The aim is to actually keep the view model free of a DisposeBag. For now, this is fine, and certainly an improvement over the original.