web-dev-qa-db-fra.com

problème avec avoir rx.tap pour UIButton dans UICollectionViewCell - RxSwift 3

Je m'abonne 2 fois pour 1 UIButton:

  1. Premier abonnement, pour mettre à jour l'interface utilisateur à chaque clic
  2. Deuxième abonnement, pour mettre à jour les valeurs sur le service Web toutes les 1 seconde après les clics cumulés.

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

}

Le problème:

Étant donné que la suppression se produit sur prepareForReuse(), si je clique plusieurs fois sur le bouton et que je défile immédiatement, les appels du service Web sont supprimés et ne sont pas mis à jour.

ce que j'ai essayé:

  1. Ajout de addDisposableTo(vc?.rx_disposableBag) au ViewController DisposableBag parent.

    Le problème , les abonnements accumulés et à chaque clic la updateWS() appelée plusieurs fois qui est inscrite sur chaque parchemin et jamais éliminée.

  2. J'ai essayé de supprimer la réinitialisation de disposableBag de prepareForReuse().

    Le problème , Encore une fois, les abonnements aux boutons sont dupliqués et accumulés et de nombreux appels de service Web sont appelés à chaque clic.

Question: Comment puis-je obtenir les abonnements debounce appelés à la fin et jamais répétés avec plusieurs abonnements (dans le cas de addDisposableTo viewController Bag)?

9
MBH

Puisque prepareButton() est toujours appelé dans le (cellForItemAt indexPath: IndexPath) de la vue de collection, vous pouvez essayer ceci:

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
}

Supprimez l'implémentation prepareForReuse().

8
XFreire

addDisposableTo(vc?.rx_disposableBag) ajouté au ViewController DisposableBag parent.

Le problème, les abonnements accumulés et à chaque clic le updateWS () appelé plusieurs fois qui est abonné à chaque défilement et jamais supprimé.

Il est possible que votre self.updateOnWS() soit appelé plusieurs fois en raison de la façon dont vous vous abonnez au toucher du bouton.

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

Comme vous pouvez le voir, vous vous abonnez à tous les événements à l'aide de la méthode subscribe(). Cela signifie que tous les événements Rx (onNext, onError, onCompleted, onSubscribed et onDisposed) déclenchent le self.updateOnWS(). Vous pouvez vérifier si c'est le cas en imprimant l'objet event pour voir quel événement a été déclenché.

Abonnez-vous sur onNext uniquement

Un correctif possible pourrait être de ne souscrire qu'à l'opération onNext.

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

En utilisant le DisposeBag du contrôleur de vue, vous pouvez vous assurer que l'opération continue toujours même si la cellule est supprimée (lorsque vous faites défiler vers le bas). Toutefois, si vous en avez besoin pour supprimer l'abonnement lorsque la cellule est supprimée, utilisez le DisposeBag de la cellule, pas le contrôleur de vue.

Note annexe - fuite de mémoire

Notez que la référence à self est spécifiée comme étant faible, afin que vous puissiez éviter les fuites de mémoire. En spécifiant qu'il est faible, il vous fournira une référence à soi qui est facultative.

Sans cela, la fermeture que vous avez créée pour le bloc onNext conservera une référence forte au self qui est votre UICollectionViewCell, qui à son tour est propriétaire de la fermeture dont nous discutons .

Cela entraînera éventuellement un crash de mémoire insuffisante. Consultez la référence que j'ai publiée dans les commentaires de votre question pour plus d'informations sur les fuites de mémoire causées par un mauvais référencement de soi.

7
iwillnot