iosswiftrx-swiftviper-architecture

Should I use a PublishSubject if I am notifying an action is complete?


I am learning Viper w/ RxSwift.

I would like to notify my Presenter that viewDidLoad was called in my ViewController.

To do this I have the following:

class LoginPresenter {

    weak var view: LoginView?
    var interactor: LoginUseCase?
    var router: LoginRouter?

    private(set) var viewDidLoad = PublishSubject<Void>()

    private lazy var disposeBag = DisposeBag()

    required init(view: LoginView?, interactor: LoginUseCase?, router: LoginRouter?) {
        self.view = view
        self.interactor = interactor
        self.router = router

        viewDidLoad
            .subscribe(onNext: { _ in
                // do something on viewDidLoad
            }).disposed(by: disposeBag)
    }
}
class LoginViewController: UIViewController {

    var presenter: LoginPresenter?

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter?.viewDidLoad.onNext(())
    }
}

Once my view is loaded I am calling presenter?.viewDidLoad.onNext(())

I am then able to trigger any actions within my presenter, such as calling out to my router to ensure navigation is configured or my interactor.

Should I be using a PublishSubject for this? Or does RxSwift have a better suited type?

I feel like this approach means I will end up with something like

        viewDidLoad
            .subscribe(onNext: { _ in
                self.router?.viewDidLoad.onNext(())
            }).disposed(by: disposeBag)

Solution

  • Hmm... A Presenter's job is to gather up user actions and I'm not so sure we should consider viewDidLoad a user action. And in any case, the Wireframe (which handles routing) shouldn't need to know when viewDidLoad is called in the first place; its job is to present new screens and you can't present a screen in viewDidLoad.

    That said, you can setup your connection in the ViewController's presenter didSet:

    final class ViewController: UIViewController {
    
        var presenter: Presenter? {
            didSet {
                guard let presenter = presenter else { viewDidLoadDisposable.dispose(); return }
                viewDidLoadDisposable.disposable = rx.methodInvoked(#selector(viewDidLoad))
                    .map { _ in }
                    .bind(to: presenter.viewDidLoad)
            }
        }
    
        let viewDidLoadDisposable = SerialDisposable()
    
        deinit {
            viewDidLoadDisposable.dispose()
        }
    }
    
    final class Presenter {
        let viewDidLoad = PublishSubject<Void>()
    }
    

    In general though, it is in the viewDidLoad where the presenter and viewController elements are normally bound together so the above code has a pretty unnatural feel.

    Also, Observables, Subjects and the DisposeBag should not be vars, use lets instead. That's the "functional" part of functional reactive programming.