web-dev-qa-db-ja.com

UICollectionViewCellのUIButtonのrx.tapの問題-RxSwift 3

1つのUIButtonを2回サブスクライブしています:

  1. クリックごとにUIを更新するための最初のサブスクリプション
  2. 2番目のサブスクリプション。累積されたクリック後、1秒ごとにWebサービスの値を更新します。

コード:

_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()
    }

}
_

問題:

破棄はprepareForReuse()で発生するため、ボタンを何度もクリックしてすぐにスクロールすると、Webサービス呼び出しが破棄され、更新されません。

私が試したこと:

  1. 親ViewController DisposableBagにaddDisposableTo(vc?.rx_disposableBag)を追加しました。

    問題、サブスクリプションが累積し、クリックするたびにupdateWS()が何度も呼び出され、スクロールごとにサブスクライブされ、破棄されません。

  2. prepareForReuse()からdisposableBagの再初期化を削除しようとしました。

    問題、繰り返しになりますが、ボタンへのサブスクリプションが重複して蓄積され、クリックごとに多くのWebサービス呼び出しが呼び出されます。

質問:debounceサブスクリプションを最後まで呼び出し、複数のサブスクリプションで繰り返されないようにするにはどうすればよいですか(addDisposableTo viewController Bagの場合)?

9
MBH

prepareButton()は常にコレクションビューの(cellForItemAt indexPath:IndexPath)で呼び出されるため、これを試すことができます。

_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
}
_

prepareForReuse()実装を削除します。

8
XFreire

親のViewController DisposableBagにaddDisposableTo(vc?.rx_disposableBag)を追加しました。

問題は、サブスクリプションが累積し、クリックするたびにupdateWS()が何度も呼び出され、スクロールごとにサブスクライブされて破棄されないことです。

ボタンのタップをサブスクライブする方法が原因で、self.updateOnWS()が何度も呼び出される可能性があります。

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

ご覧のとおり、subscribe()メソッドを使用してすべてのイベントをサブスクライブします。これは、すべてのRxイベント(onNextonErroronCompletedonSubscribed、およびonDisposed)がself.updateOnWS()。これが当てはまるかどうかを確認するには、eventオブジェクトを出力して、トリガーされたイベントを確認します。

onNextでのみ購読

可能な修正は、onNextオペレーションのみをサブスクライブすることです。

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

ビューコントローラーのDisposeBagを使用することにより、セルが破棄された場合でも(下にスクロールしたときに)操作が続行されることを確認できます。ただし、セルが破棄されたときにサブスクリプションを破棄する必要がある場合は、ビューコントローラーではなく、セルのDisposeBagを使用してください。

付記-メモリリーク

selfへの参照は弱く指定されているため、メモリリークの発生を防ぐことができます。弱く指定することで、オプションである自分自身への参照を提供します。

これを行わないと、onNextブロック用に作成したクロージャは、selfへの強い参照を保持します。これは、UICollectionViewCellであり、これが、今説明しているクロージャそのものを所有しています。 。

これにより、最終的にメモリ不足によるクラッシュが発生します。自分自身を誤って参照することによって引き起こされるメモリリークについての詳細は、質問のコメントに投稿したリファレンスを参照してください。

7
iwillnot