1つのUIButtonを2回サブスクライブしています:
コード:
_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サービス呼び出しが破棄され、更新されません。
私が試したこと:
親ViewController DisposableBagにaddDisposableTo(vc?.rx_disposableBag)
を追加しました。
問題、サブスクリプションが累積し、クリックするたびにupdateWS()
が何度も呼び出され、スクロールごとにサブスクライブされ、破棄されません。
prepareForReuse()
からdisposableBagの再初期化を削除しようとしました。
問題、繰り返しになりますが、ボタンへのサブスクリプションが重複して蓄積され、クリックごとに多くのWebサービス呼び出しが呼び出されます。
質問:debounce
サブスクリプションを最後まで呼び出し、複数のサブスクリプションで繰り返されないようにするにはどうすればよいですか(addDisposableTo
viewController Bagの場合)?
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()
実装を削除します。
親の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イベント(onNext
、onError
、onCompleted
、onSubscribed
、および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
であり、これが、今説明しているクロージャそのものを所有しています。 。
これにより、最終的にメモリ不足によるクラッシュが発生します。自分自身を誤って参照することによって引き起こされるメモリリークについての詳細は、質問のコメントに投稿したリファレンスを参照してください。