Search code examples
iosswiftrx-swiftdispose

Why are not my subscriptions not disposed even though disposebag is hooked and my view is dismissed?


I recently noticed that none of my subscriptions are actually disposed even though I hook .disposed(by: disposeBag). Even if I leave the view or dismiss the modal completely, the subscriptions refuse to dispose.

I have my views in a tabBar which keeps the subscriptions alive I suppose, even though I leave the view, but even so, this tabBar is in a modal and when the modal is dismissed shouldn't the subscriptions dispose on their own accord?

One way around this is to manually dispose all subscriptions on viewWillDisappear, but I would still like to know why this issue persists.

private func noErrorText() {
    viewModel.activeErrorContent.debug("--debugNoErrorText").subscribe(onNext: { [weak self] cells in
        self?.noErrorView.isHidden = !cells.isEmpty 
    }).disposed(by: disposeBag)
}

Which gives the output:

2022-03-25 04:26:55.219: --debugNoErrorText -> subscribed 2022-03-25 04:26:55.219: --debugNoErrorText -> Event next([])

Let me know if there is anything else that I should provide or explain.

EDIT In response to the comments:

The disposeBag is in a superClass and my subscriptions and disposed(by:) are in a subClass. Not sure if that's relevant.

final class TechnicianDashboardViewController: BaseViewController {...

Here are my subscriptions:

If I understand it correctly with strong references, then self.disposeBag in the first snippet creates a strong reference to the subView.

extension TechnicianDashboardViewController: PinCodeViewControllerDelegate, UITableViewDelegate {
    func completed(currentPin: String?, newPin: String) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            guard let self = self else { return }
            self.resetWithOverlay(pin: newPin)
                .subscribe().disposed(by: self.disposeBag)
        }
    }
}

Then there are these as well. All use the same disposeBag. None of them are disposed.

private func noErrorText() {
        viewModel.activeErrorContent.subscribe(onNext: { [weak self] cells in
            self?.noErrorView.isHidden = !cells.isEmpty
        }).disposed(by: disposeBag)
    }
    
private func getErrors() {
    viewModel.activeErrorContent.bind(to: activeErrorTableView.rx
                                            .items(cellIdentifier: ErrorsTableViewCell.identifier,
                                                   cellType: ErrorsTableViewCell.self)) { row, data, cell in
                                                       cell.rowService = row
                                                       cell.viewModel = data
    }.disposed(by: disposeBag)
}
    
private func getEvents() {
    viewModel.activeEventContent.bind(to: activeEventTableView.rx
                                            .items(cellIdentifier: EventStatusTableViewCell.identifier,
                                                   cellType: EventStatusTableViewCell.self)) { row, data, cell in
                                                       cell.viewModel = data
                                                       cell.rowService = row
    }.disposed(by: disposeBag)
}

Solution

  • So I figured out the cause and solution to why the subscriptions don't dispose, much thanks to @Daniel T. I'll write the answer here in case someone have a similar problem.

    So basically I added a print to deinit like this:

    deinit {
        print("-- DEINIT")
    }
    
    

    And it turns out that it's not triggered at all (Well, they are triggered right at the start before anything is loaded, but not when you exit the view and I think this is because of the views being in a tabBar controller, but that's another topic). And this goes for several views. This means that the subscriptions aren't disposed, because that happens in the deinit.

    So the next question is, why isn't deinit triggered. That is because there's a memory leak preventing the ARC to release the reference to the view. The memory leak can be caused by different things, but for most of the time it's because you forget to add [weak self] to a closure. If you just forget it at a single place, you can block deinit from triggering. In my case, my mistakes were often "hidden" in the super class, making it less obvious.

    So, fixing the memory leak so that deinit trigger, will then dispose your subscriptions like normal, preventing even further memory leaks.