RxSwift La suppression d'un abonnement appelle la suppression d'un autre abonnement

Aug 18 2020

J'ai un PublishSubject<InfoData>dans un ViewController. Et je m'y abonne, donc quand il émet un événement - je montre le 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)
}

Dans un ViewController, j'ai un tableView avec des en-têtes de section. La vue d'en-tête de section a un infoMessageAction: PublishSubject<InfoData?>. Lors du lancement d'une vue pour viewForHeaderInSectionje fais un abonnement entre le infoMessageActionet 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
}

Lorsque la vue d'en-tête est lancée pour la première fois, tout fonctionne correctement - infoMessageActiondéclenche le infoDataqui à son tour déclenche la présentation d'AlertViewController. Lorsque je fais défiler la vue d'en-tête au-delà de l'écran, l'abonnement entre view.infoMessageActionet infoDatasupprime (ce qui est le comportement attendu car la vue a été définie).

Mais je reçois également l'abonnement entre infoDataet ViewController. Je reçois event completedet disposepour l' abonnement view.infoMessageAction<-> infoDataet aussi event completedet disposepour l' infoDataabonnement <-> ViewController.

Je m'attends à ce que seul l' abonnement view.infoMessageAction<-> infoDatasoit interrompu. Les deux abonnements sont également supprimés par disposerBag. Pourquoi l' infoDataabonnement <-> ViewController est-il supprimé et comment l'empêcher?

Merci d'avance!

Réponses

DanielT. Aug 18 2020 at 18:28

Lorsque votre FutureSpendingsHeaderViewest désinitialisé, la vue qui est la source de infoMessageActionest également en cours de désinitialisation, et cette vue émet un completedévénement à ce moment-là. Cet événement terminé est transmis à infoDataqui émet alors son propre événement terminé.

Une fois qu'un observable a émis un événement terminé, c'est fait. Il ne peut plus émettre d'événements. Donc, l'abonnement à celui-ci est supprimé.

Votre réponse @Alex change l'équation en modifiant l'ordre dans lequel les éléments de votre vue sont désinitialisés. Le disposeBag est désinitialisé en premier maintenant, ce qui rompt la chaîne observable avant que la vue envoie l'événement terminé.

Une meilleure solution serait d'utiliser un PublishRelayplutôt qu'un PublishSubject. Les relais n'émettent pas d'événements terminés.

Mieux encore, se débarrasser complètement du sujet et faire quelque chose comme:

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
}
Alex Aug 18 2020 at 16:20

Trouvé le problème, si quelqu'un fait face à une telle situation. Dans la vue d'en-tête de section, j'ai commencé disposeBagcomme constante et j'ai pensé que tout le reste était géré par RxSwift lui-même lorsque la vue était définie. J'ai donc mis à jour la vue pour:

var disposeBag = DisposeBag()
    
deinit {
    disposeBag = DisposeBag()
}

Maintenant, l'abonnement est supprimé selon les besoins.