Reputation: 25001
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
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
. UnlikePublishSubject
it can't terminate with error or completed.
Upvotes: 4