pgb
pgb

Reputation: 25001

RxSwift PublishSubject getting unintentionally disposed

I have a View Controller that opens a modal view controller for the user to pick images from its library. For that, I'm using a Rx wrapper of DKImagePickerController that I wrote.

The relevant code in the View Controller is as follows:

fileprivate func addPicturesFromLibrary() {
    guard let viewModel = self.viewModel else { return }

    let pickerController = DKImagePickerController()
    pickerController.singleSelect = false
    pickerController.maxSelectableCount = 10
    pickerController.showsCancelButton = true
    pickerController.sourceType = .photo

    pickerController.rx.didSelectAssets()
        .debug("👍 1")
        .bind(to: viewModel.addAssetsInput)
        .disposed(by: disposeBag)

    self.present(pickerController, animated: true, completion: nil)
}

My view model looks like this:

class MediaViewModel {

      var selectedImages = Variable<[MediaListingImage]>([])

      public let addAssetsInput = PublishSubject<DKAsset>()

      init() {
         bind()
      }

      private func bind() {
          addAssetsInput
              .debug("👍 2")
              .flatMap {
                  $0.rx.fetchOriginalImage()
              }
              .map {
                  MediaListingImage.local($0)
              }
              .subscribe(onNext: { [weak self] (mediaListingImage) in
                  self?.selectedImages.value.append(mediaListingImage)
              })
              .disposed(by: disposeBag)
      }
}

When I open the first modal picker, it works as expected. However, when it disposes, the binding in the view model also disposes, so subsequent presentations of the modal view controller won't work.

Here's the log I get in the console, which might help you understand what I see:

2017-11-15 17:33:15.490: 👍 2 -> subscribed
2017-11-15 17:33:21.452: 👍 1 -> subscribed
2017-11-15 17:33:23.902: 👍 1 -> Event next(<DKImagePickerController.DKAsset: 0x6080002ab940>)
2017-11-15 17:33:23.902: 👍 2 -> Event next(<DKImagePickerController.DKAsset: 0x6080002ab940>)
2017-11-15 17:33:23.902: 👍 1 -> Event completed
2017-11-15 17:33:23.903: 👍 2 -> Event completed
2017-11-15 17:33:23.903: 👍 2 -> isDisposed
2017-11-15 17:33:23.903: 👍 1 -> isDisposed
2017-11-15 17:33:29.924: 👍 1 -> subscribed
2017-11-15 17:33:33.114: 👍 1 -> Event next(<DKImagePickerController.DKAsset: 0x60c0002a62a0>)
2017-11-15 17:33:33.114: 👍 1 -> Event completed
2017-11-15 17:33:33.114: 👍 1 -> isDisposed

If I change the code in the View Controller to something like:

    pickerController.rx.didSelectAssets()
        .debug("👍 1")
        .subscribe(onNext: { (asset) in
            viewModel.addAssetsInput.onNext(asset)
        })
        .disposed(by: disposeBag)

it works as expected. However, I find using bind more elegant in this case, and would like to keep using it if possible.

What is triggering the binding of the PublishSubject in the view model to dispose? How can I prevent it without leaking resources?

Upvotes: 2

Views: 1707

Answers (1)

jefflovejapan
jefflovejapan

Reputation: 2121

I suspect that because the completed propagated through the second chain (the one in MediaViewModel.bind) that it will no longer receive any events. I think you'd want to use subscribe(onNext: { ... }) here instead of bind so that you can avoid sending error and completion events.

Another alternative would be to use a PublishRelay instead of a PublishSubject.

PublishRelay is a wrapper for PublishSubject. Unlike PublishSubject it can't terminate with error or completed.

Upvotes: 4

Related Questions