Search code examples
iosswiftmvvmrx-swiftreactive-cocoa

rx.sentMessage(#selector(UIViewController.viewDidLoad)) is not firing


I have an app using RxSwift following MVVM.

ViewController


import UIKit
import RxSwift
import RxCocoa

final class ProfileViewController: BaseViewController<ProfileView> {

    var viewModel: Attachable<ProfileViewModel>!
    var bindings: ProfileViewModel.Bindings {

        let viewDidLoad = rx.sentMessage(#selector(UIViewController.viewDidLoad))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        let viewWillDisappear = rx.sentMessage(#selector(UIViewController.viewWillDisappear))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        let logout = navigationItem.rightBarButtonItem?.rx.tap
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        return ProfileViewModel.Bindings(
            connectStore: viewWillAppear.asDriver(),
            disconnectStore: viewWillDisappear.asDriver(),
            fetchProfileByUserId: viewDidLoad.asDriver()
        )
    }

    private lazy var disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        configureView()
    }


    private func configureView() {
        navigationItem.title = "Your account"
    }
}

extension ProfileViewController: ViewModelAttaching {
    func bind(viewModel: ProfileViewModel) -> ProfileViewModel {

        return viewModel
    }
}

ViewModel


import RxSwift
import RxCocoa
import ReSwift

final class ProfileViewModel: ViewModelType {

    typealias Dependency = HasReduxStore & HasImagePicker

    lazy var userID: String = __user__.id

    let data = PublishSubject<ProfileHeaderViewModel>()
    let endSession = PublishSubject<Void>()

    private let dependency: Dependency

    struct Bindings {
        let connectStore: Driver<Void>
        let disconnectStore: Driver<Void>
        let fetchProfileByUserId: Driver<Void>
    }

    private lazy var disposeBag = DisposeBag()

    init(dependency: Dependency, bindings: Bindings) {
        self.dependency = dependency

        bindings.connectStore
            .asObservable()
            .map { self }
            .bind(onNext: dependency.store.subscribe)
            .disposed(by: disposeBag)

        bindings.disconnectStore
            .asObservable()
            .map { self }
            .bind(onNext: dependency.store.unsubscribe)
            .disposed(by: disposeBag)

        bindings.fetchProfileByUserId
            .asObservable()
            .bind(onNext: dispatchFetchForProfileById)
            .disposed(by: disposeBag)
    }

    private func dispatchFetchForProfileById() {
        dependency.store.dispatch(FetchProfileByUserId(payload: userID))
    }
}

extension ProfileViewModel: StoreSubscriber {
    func newState(state: AppState) {

    }
}

When my view is loaded I would expect bindings.fetchProfileByUserId to be invoked. However nothing is happening. I updated my binding to trigger on viewWillAppear instead and everything worked as expected.

I do not know why viewDidLoad is not working in this case.


Solution

  • You can add .debug() to your viewDidLoad chain, as well as a print() to viewDidLoad method to see what exactly happens, but by the looks of it, it seems you're setting up your bindings after viewDidLoad (in the configureView maybe?) has fired so you never get any additional events, this happens only once during the view controller creation and would explain why viewDidAppear works.