RxSwift L'eliminazione di una sottoscrizione richiama la disposizione di un'altra sottoscrizione

Aug 18 2020

Ho un PublishSubject<InfoData>in un ViewController. E mi iscrivo ad esso, quindi quando emette un evento, mostro l'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 un ViewController ho un tableView con intestazioni di sezione. La vista dell'intestazione della sezione ha un'estensione infoMessageAction: PublishSubject<InfoData?>. Quando si avvia una visualizzazione per viewForHeaderInSectioneffettuo un abbonamento tra infoMessageActione 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
}

Quando la visualizzazione dell'intestazione viene avviata per la prima volta, tutto funziona correttamente, infoMessageActionattiva il infoDatache a sua volta attiva la presentazione di AlertViewController. Quando scorro la vista dell'intestazione oltre lo schermo l'abbonamento tra view.infoMessageActione infoDatadispone (che è un comportamento previsto quando la vista è stata definita).

Ma ho eliminato anche l'abbonamento tra infoDatae ViewController. Ricevo event completede disposeper view.infoMessageAction<-> infoDataabbonamento e anche event completede disposeper infoData<-> abbonamento a ViewController.

Mi aspetto che solo view.infoMessageAction<-> l' infoDataabbonamento debba interrompersi. Inoltre entrambi gli abbonamenti vengono smaltiti da disposeBag differenti. Perché l' infoDataabbonamento a <-> ViewController viene eliminato e come prevenirlo?

Grazie in anticipo!

Risposte

DanielT. Aug 18 2020 at 18:28

Quando la tua FutureSpendingsHeaderViewè deinizializzata, qualunque vista che è la fonte infoMessageActionviene anche deinizializzata, e quella vista emette un completedevento in quel momento. L'evento completato viene trasmesso al infoDataquale emette il proprio evento completato.

Una volta che un Observable ha emesso un evento completato, è fatto. Non può emettere altri eventi. Quindi l'abbonamento ad esso viene smaltito.

La tua risposta @Alex cambia l'equazione cambiando l'ordine in cui le cose all'interno della tua vista vengono deinizializzate. DisposeBag viene ora deinizializzato per primo, interrompendo la catena osservabile prima che la vista invii l'evento completato.

Una soluzione migliore sarebbe usare a PublishRelaypiuttosto che a PublishSubject. I relè non emettono eventi completati.

Anche meglio di così sarebbe sbarazzarsi completamente del soggetto e fare qualcosa come:

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

Trovato il problema, se qualcuno deve affrontare una situazione del genere. Nella vista dell'intestazione della sezione ho iniziato disposeBagcome costante e ho pensato che tutto il resto fosse gestito da RxSwift stesso quando la vista è stata definita. Quindi ho aggiornato la vista a:

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

Ora l'abbonamento viene smaltito secondo necessità.