RxSwift La eliminación de una suscripción invoca la eliminación de otra suscripción

Aug 18 2020

Tengo PublishSubject<InfoData>un ViewController. Y me suscribo a él, por lo que cuando emite un evento, muestro el 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)
}

En un ViewController tengo un tableView con encabezados de sección. La vista de encabezado de sección tiene una extensión infoMessageAction: PublishSubject<InfoData?>. Al iniciar una vista viewForHeaderInSection, hago una suscripción entre el infoMessageActiony 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
}

Cuando la vista de encabezado se inicia por primera vez, todo funciona bien, infoMessageActionactiva la infoDataque a su vez activa la presentación de AlertViewController. Cuando desplazo la vista del encabezado más allá de la pantalla, la suscripción entre view.infoMessageActiony se infoDataelimina (que es el comportamiento esperado cuando se definió la vista).

Pero también me deshago de la suscripción entre infoDataViewController. Recibo event completedy disposepara la suscripción view.infoMessageAction<-> infoDatay también event completedy disposepara la infoDatasuscripción <-> ViewController.

Espero que solo se rompa la suscripción view.infoMessageAction<-> infoData. También ambas suscripciones dispuestas por diferentes disposeBag. ¿Por qué se infoDataelimina la suscripción <-> ViewController y cómo evitarlo?

¡Gracias por adelantado!

Respuestas

DanielT. Aug 18 2020 at 18:28

Cuando FutureSpendingsHeaderViewse desinicializa su, cualquier vista que sea el origen infoMessageActiontambién se desinicializa, y esa vista emite un completedevento en ese momento. Ese evento completado se transmite al infoDataque luego emite su propio evento completado.

Una vez que un Observable ha emitido un evento completo, está hecho. No puede emitir más eventos. De modo que se elimina la suscripción.

Su respuesta @Alex cambia la ecuación al cambiar el orden en que las cosas dentro de su vista se desinicializan. DisposeBag se está desinicializando primero ahora, lo que rompe la cadena observable antes de que la vista envíe el evento completo.

Una mejor solución sería utilizar un en PublishRelaylugar de un PublishSubject. Los relés no emiten eventos completados.

Incluso mejor que eso sería deshacerse del tema por completo y hacer algo como:

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

Encontré el problema, si alguien se enfrenta a tal situación. En la vista de encabezado de sección, inicié disposeBagcomo constante y pensé que todo lo demás lo maneja RxSwift cuando se define la vista. Así que actualicé la vista a:

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

Ahora la suscripción se elimina según sea necesario.