Search code examples
rx-swiftsubscription

RxSwift Disposing one subscription invokes dispose of another subscription


I have a PublishSubject<InfoData> in a ViewController. And I subscribe to it, so when it emits an event - I show the UIAlertViewController.

let infoData = PublishSubject<InfoData>()
private func bindInfoData() {
     infoData.subscribe(onNext: { [weak self] (title, message) in
         self?.presentInfoSheetController(with: title, message: message)
     }).disposed(by: disposeBag)
}

In a ViewController I have a tableView with section headers. Section header view has a infoMessageAction: PublishSubject<InfoData?>. When initiating a view for viewForHeaderInSection I make a subscription between the infoMessageAction and infoData.

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
      let view = FutureSpendingsHeaderView(frame: frame)
      view.infoMessageAction
            .compactMap { $0 }
            .bind(to: infoData)
            .disposed(by: view.disposeBag)
      return view
}

When the header view initiated for the first time all works good - infoMessageAction triggers the infoData which in turn triggers presentation of AlertViewController. When I scroll header view beyond the screen the subscription between view.infoMessageAction and infoData disposes (which is expected behavior as the view was deinited).

But I get disposed the subscription between infoData and ViewController as well. I receive event completed and dispose for view.infoMessageAction <-> infoData subscription and also event completed and dispose for infoData <-> ViewController subscription.

I expect that only view.infoMessageAction <-> infoData subscription should break. Also both subscriptions disposed by different disposeBag. Why is infoData <-> ViewController subscription get disposed and how to prevent it?

Thanks in advance!


Solution

  • When your FutureSpendingsHeaderView is deinitialized, whatever view that is the source of infoMessageAction is also being deinitialized, and that view emits a completed event at that time. That completed event is passed on to infoData which then emits its own completed event.

    Once an Observable has emitted a completed event, it is done. It can't emit any more events. So the subscription to it is disposed.

    Your answer @Alex changes the equation by changing the order that things inside your view get deinitialized. The disposeBag is getting deinitialized first now, which breaks the observable chain before the view sends the completed event.

    A better solution would be to use a PublishRelay rather than a PublishSubject. Relays don't emit completed events.

    Even better than that would be to get rid of the subject entirely and do something like:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = FutureSpendingsHeaderView(frame: frame)
        view.infoMessageAction
            .compactMap { $0 }
            .subscribe(onNext: { [weak self] (title, message) in
                self?.presentInfoSheetController(with: title, message: message)
            })
            .disposed(by: view.disposeBag)
        return view
    }