RxSwift Descartar uma assinatura invoca a disposição de outra assinatura

Aug 18 2020

Eu tenho um PublishSubject<InfoData>em um ViewController. E eu me inscrevo nele, então quando ele emite um evento - eu mostro o 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)
}

Em um ViewController, tenho um tableView com cabeçalhos de seção. A visualização do cabeçalho da seção possui um infoMessageAction: PublishSubject<InfoData?>. Ao iniciar uma visualização para viewForHeaderInSection, faço uma assinatura entre 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 a visualização do cabeçalho é iniciada pela primeira vez, tudo funciona bem - infoMessageActionaciona o infoDataque, por sua vez, aciona a apresentação de AlertViewController. Quando eu rolar a exibição do cabeçalho além da tela, a assinatura entre view.infoMessageActione infoDatadescarta (o que é o comportamento esperado quando a exibição foi definida).

Mas eu descarto a assinatura entre infoDatae ViewController também. Eu recebo event completede disposepara assinatura view.infoMessageAction<-> infoDatae também event completede disposepara infoDataassinatura <-> ViewController.

Espero que apenas view.infoMessageAction<-> a infoDataassinatura seja interrompida. Além disso, ambas as assinaturas disponibilizadas por diferentes disposeBag. Por que a infoDataassinatura do <-> ViewController foi descartada e como evitá-lo?

Desde já, obrigado!

Respostas

DanielT. Aug 18 2020 at 18:28

Quando o seu FutureSpendingsHeaderViewé desinicializado, qualquer visão que seja a fonte infoMessageActiontambém está sendo desinicializada e essa visão emite um completedevento naquele momento. Esse evento concluído é passado para o infoDataqual então emite seu próprio evento concluído.

Uma vez que um Observable tenha emitido um evento completo, está feito. Não pode emitir mais eventos. Portanto, a assinatura para ele é descartada.

Sua resposta @Alex muda a equação, alterando a ordem em que as coisas dentro de sua visualização são desinicializadas. O disposeBag está sendo desinicializado primeiro agora, o que quebra a cadeia observável antes que a visualização envie o evento concluído.

Uma solução melhor seria usar um em PublishRelayvez de um PublishSubject. Os relés não emitem eventos concluídos.

Ainda melhor do que isso seria se livrar totalmente do assunto e fazer 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

Encontrou o problema, se alguém enfrentar tal situação. Na visão do cabeçalho da seção, iniciei disposeBagcomo constante e pensei que todo o resto é tratado pelo próprio RxSwift quando a visão é definida. Então, atualizei a visualização para:

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

Agora, a assinatura é descartada conforme necessário.