Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

issue with having rx.tap for UIButton in UICollectionViewCell - RxSwift 3

I am subscribing 2 times for 1 UIButton :

  1. First subscription, for updating UI on every click
  2. Second subscription, for updating the values on Web Service every 1 second after accumulated clicks.

Code:

class ProductionSize {
    var id : Int?
    var size: Int = 0
    var name: String = ""
}

class ProductionCell: UICollectionViewCell {
    var rxBag = DisposeBag()


    // this will be set in the (cellForItemAt indexPath: IndexPath) of collection view
    var productionSize: ProductionSize? {
        didSet {
            showProductionSize()
            prepareButton()
        }
    }

    func showProductionSize() {
        // ... code for showing ProductionSize in labels
    }

    func prepareButton() {
        // This for subscribing for every click for displaying purpose

        btn_increase.rx.tap
            .subscribe(){event in 
                self.increaseClicked() 
            }
            .addDisposableTo(rxBag)

        // this for subscribing for sending webservice request after 1 second of clicking the button (so that if user click it quickly i send only last request)

        btn_increase.rx.tap
            .debounce(1.0, scheduler: MainScheduler.instance)
            .subscribe(){ event in self.updateOnWS() }
            .addDisposableTo(rxBag)
    }

    func increaseClicked() {
        productionSize.size = productionSize.size + 1
        showProductionSize()
    }

    func updateOnWS() {
        // code for updating on webservice with Moya, RxSwift and Alamofire§
    }


    // when scrolling it gets called to dispose subscribtions
    override func prepareForReuse() {
        rxBag = DisposeBag()
    }

}

The problem:

Since the dispose happens on prepareForReuse(), If i click on the button many times and scroll immediately, the webservice calls gets disposed and not updated.

what I have tried:

  1. Added addDisposableTo(vc?.rx_disposableBag) to the parent ViewController DisposableBag.

    The problem, the subscribtions accumulated and on every click the updateWS() called many times which is subscribed on every scroll and never disposed.

  2. I have tried to remove the disposableBag re-initialization from prepareForReuse().

    The problem, Again the subscriptions to the buttons getting duplicated and accumulated and many webservice calls get called every click.

Question: How can I get the debounce subscriptions called to the end and never repeated with multiple subscriptions (in case of addDisposableTo viewController Bag) ?

like image 432
MBH Avatar asked Apr 04 '17 12:04

MBH


2 Answers

Since prepareButton() is always called in the (cellForItemAt indexPath: IndexPath) of collection view you could try this:

func prepareButton() {

    self.rxBag = nil 
    let rxBag = DisposeBag()

    // This for subscribing for every click for displaying purpose

    btn_increase.rx.tap
        .subscribe(onNext: { [weak self] _ in 
            self?.increaseClicked()
        })
        .addDisposableTo(rxBag)

    // this for subscribing for sending webservice request after 1 second of clicking the button (so that if user click it quickly i send only last request)

    btn_increase.rx.tap
        .debounce(1.0, scheduler: MainScheduler.instance)
        .subscribe(onNext: { [weak self] _ in 
            self?.updateOnWS()
        })
        .addDisposableTo(rxBag)

    self.rxBag = rxBag
}

Remove the prepareForReuse() implementation.

like image 81
xandrefreire Avatar answered Nov 01 '22 16:11

xandrefreire


Added addDisposableTo(vc?.rx_disposableBag) to the parent ViewController DisposableBag.

The problem, the subscribtions accumulated and on every click the updateWS() called many times which is subscribed on every scroll and never disposed.

It is possible that your self.updateOnWS() gets called many times because of how you subscribe to the button's tap.

    btn_increase.rx.tap
        .debounce(1.0, scheduler: MainScheduler.instance)
        .subscribe(){ event in self.updateOnWS() }
        .addDisposableTo(rxBag)

As you can see, you subscribe to all events using the subscribe() method. This means that all Rx events (onNext, onError, onCompleted, onSubscribed, and onDisposed) trigger the self.updateOnWS(). You can check if this is the case by printing out the event object to see what event was triggered.

Subscribe on onNext only

A possible fix might be to only subscribe to the onNext operation.

    btn_increase.rx.tap
        .debounce(1.0, scheduler: MainScheduler.instance)
        .subscribe(onNext: { [weak self] (_ : Void) in
               self?.updateOnWS() 
        })
        .addDisposableTo(vc?.rxdisposableBag)

By using the DisposeBag of the view controller, you can make sure that the operation still continues even if the cell gets disposed (when you scroll down). However, if you need it to dispose the subscription when the cell gets disposed, use the DisposeBag of the cell, not the view controller.

Side note - Memory leak

Notice that the reference to self is specified to be weak, so that you can prevent memory leaks from happening. By specifying it to be weak, it will provide you a reference to self that is optional.

Without doing this, the closure you created for the onNext block will retain a strong reference to the self which is your UICollectionViewCell, which in turn owns the very closure we are discussing.

This will eventually cause a out of memory crash. See the reference I posted on the comments of your question for more things to read about memory leaks caused by incorrectly referencing self.

like image 41
dsapalo Avatar answered Nov 01 '22 16:11

dsapalo