在 UICollectionViewCell 中为 UIButton 设置 rx.tap 的问题 - RxSwift 3

Posted

技术标签:

【中文标题】在 UICollectionViewCell 中为 UIButton 设置 rx.tap 的问题 - RxSwift 3【英文标题】:issue with having rx.tap for UIButton in UICollectionViewCell - RxSwift 3 【发布时间】:2017-04-04 12:09:49 【问题描述】:

我为 1 个 UIButton 订阅了 2 次:

    首次订阅,每次点击都会更新 UI 二次订阅,累计点击后每1秒更新一次Web Service上的值。

代码:

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(),如果我多次单击按钮并立即滚动,webservice 调用将被处理而不更新。

我尝试过的:

    在父 ViewController DisposableBag 中添加了addDisposableTo(vc?.rx_disposableBag)

    问题,订阅累积,每次点击updateWS()都会被调用很多次,每次滚动都订阅了,从未被释放。

    我已尝试从prepareForReuse() 中删除disposableBag 重新初始化。

    问题,再次重复和累积按钮的订阅,并且每次点击都会调用许多 Web 服务调用。

问题: 我怎样才能让debounce 订阅被调用到最后并且从不重复多个订阅(如果是addDisposableTo viewController Bag)?

【问题讨论】:

不确定您的情况,所以请澄清一下:您的updateOnWS 中是否使用了productionSize?这样用户可以多次点击按钮,然后当他们停止点击时,数字(即点击5次)5用于网络呼叫?当您向下滚动并处置单元格时,行为应该是什么?网络请求应该被处理还是应该仍然执行?生产尺寸 UI 应该在用户点击时更新,还是在网络请求成功响应时更新? 另外,请注意在闭包中使用self 的方式,这可能会导致内存泄漏,尤其是在UITableViewCells 或UICollectionViewCells 中时。它可以防止对象被丢弃。见***.com/questions/40583685/… @iwillnot 我不会在每次点击时更新 productionSize,以便在 web 服务调用时,我将获取最后一个 productionSize 值并将其发送到 updateOnWS 中。如果点击 5 次,它只会在网络上更新 1 次。我不等待响应,为了更好的用户体验,我会显示 ui 更新它们,如果请求成功完成,我会更新它 向下滚动并处理单元格时的行为应该是什么;网络请求应该被处理还是应该仍然执行? @iwillnot 该请求应该被执行 【参考方案1】:

由于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() 实现。

【讨论】:

感谢您的回答。看起来您的代码与 prepareForReuse() 完全相同,因为每次滚动重用单元格时都会调用 prepareButton,因此它将在完成之前处理请求。无论如何我会测试它并再次分享结果 再次,如果我单击按钮并立即滚动而无需等待去抖动时间(1 秒),请求就会被取消。【参考方案2】:

在父 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 事件(onNextonErroronCompletedonSubscribedonDisposed)都会触发 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 的引用被指定为弱引用,这样可以防止发生内存泄漏。通过将其指定为弱,它将为您提供对 self 的引用,该引用是可选的。

如果不这样做,您为 onNext 块创建的闭包将保留对 self 的强引用,即您的 UICollectionViewCell,而后者又拥有我们正在讨论的闭包。

这最终会导致内存不足崩溃。请参阅我在您的问题的 cmets 上发布的参考资料,了解更多关于错误引用 self 导致的内存泄漏的信息。

【讨论】:

非常感谢您的回答。很高兴知道内存泄漏,但是当我订阅 .addDisposableTo(vc?.rxdisposableBag) 时出现了大问题。旧订阅不会在滚动上处理它会累积许多订阅和许多对 web 服务实施的调用。 我们可以在不取消旧调用的情况下进行处理吗? 您可以通过在订阅对象上调用dispose 来手动处理您的订阅。或者有一个DisposeBag,您可以重新实例化以模拟处置。 这个问题的另一种解决方案是什么? 我会考虑使用debouncethrottle 运算符,甚至可能查看bufferfilter 条件count > 0 是否得到我想要的行为。任何可能减少订阅代码多次运行的东西。对于旧订阅的处置,我会使用一个单独的处置袋,我手动清理或考虑使用 `flatMapLatest 运算符。如果答案有帮助,请将其标记为已接受。 :)

以上是关于在 UICollectionViewCell 中为 UIButton 设置 rx.tap 的问题 - RxSwift 3的主要内容,如果未能解决你的问题,请参考以下文章

我如何在 UITableViewCell 或 UICollectionViewCell 中为 UIButton 编写一个扩展,它只影响它所在的​​单元格?

在 UICollectionViewCell 的情况下无法同时满足约束

如何在 CollectionViewController 中为 gif 设置动画.. (Objective-C)

如何在 Collectionview 单元格中为 Uiimageview 设置圆角

在 UICollectionViewCell 中添加模糊

在 UICollectionViewCell 上设置 .reuseIdentifier