防止 RxSwift 中的冗余操作
Posted
技术标签:
【中文标题】防止 RxSwift 中的冗余操作【英文标题】:Prevent redundant operations in RxSwift 【发布时间】:2020-04-30 09:59:47 【问题描述】:我从 RxSwift 开始我的冒险之旅,已经对 React in js 有一点经验。我认为我的问题很常见,但我不知道如何用简洁抽象的方式描述它,所以我将在示例中描述它。
我正在构建显示一些图表的 ios 应用程序。感兴趣的部分包括 ChartAreaController、ChartInfoController,它们都嵌入在 ChartController 中。第一个控制器是显示一些图形的区域(基于 rx chartData 属性),第二个控制器将有一个滑块供用户限制显示 x 值(rx selectedXRange 属性),该值被限制在某个最小值和最大值之间。最小值/最大值由当前图表数据定义。
在 ChartController 中定义滑块更改更新图表时的行为:
override func viewDidLoad()
super.viewDidLoad()
(...)
chartInfoController.selectedXRange.asObservable()
.subscribe(onNext: [unowned self] selectedXRange in
(...)
let chartData = self.filterChartData(from: self.rawChartData, in: selectedXRange)
self.chartAreaController.chartData.accept(chartData)
).disposed(by: disposeBag)
filterChartData() 方法只是过滤掉不在范围内的数据,但为了论证的缘故,我们可以假设它的成本非常高,我不希望它在不需要时运行两次。
当用户更改他或她想要显示的图表时,新数据从服务器到达(同样是 ChartController):
private func handleNewData(_ rawChartData: ChartData)
self.rawChartData = rawChartData
guard let allowedXRange = rawChartData.xRange() else return
let selectedXRange = chartInfoController.selectedXRange.value
let newSelectedXRange = calculateSelectedXRange(currentSelectedDays: selectedDaysRange, availableDaysRange: daysRange)
let chartData = filterChartData(from: rawChartData, in: selectedXRange)
self.chartInfoController.allowedXRange = allowedXRange //this line is not crucial
self.chartInfoController.selectedXRange.accept(newSelectedXRange)
self.chartAreaController.chartData.accept(rawChartData)
因此,在新图表数据到达时,由于数据的新最小值/最大值,可能必须修剪当前选择的 xRange。因此,该方法的副作用将是更改 selectedXRange 并运行我之前粘贴的订阅。因此,当新数据到达时,chartData 会更新两次,我不希望它发生。
当然我可以注释掉handleNewData()方法的最后一行,但我不是很喜欢,因为handleNewData()存在的主要原因是设置chartData,注释掉的那行是由于该方法的副作用(即更新滑块),目标将得以实现。不可接受。
到 chartData 我还是添加了油门,因为快速移动的滑块会导致很多更新,这部分解决了我的问题(chartData 只更新了一次)。但是您可能还记得 filterChartData() 方法的成本很高,而且这部分仍然会运行两次。
所以一个问题是,如果我解决问题的总体布局是可以的,还是应该以不同的方式处理?在这一点上,我得出的结论是,我正在寻找某种方法来临时禁用 selectedXRange 上的特定订阅(不会损坏对该变量的其他订阅)。临时含义:
(...)
//disable subscription
self.chartInfoController.selectedXRange.accept(newSelectedXRange)
self.chartAreaController.chartData.accept(rawChartData)
//enable subscription
(...)
这对我来说似乎是合法的,因为 ChartController 作为订阅的所有者和值的更改者可能希望在适合他时禁用订阅(它?)。
RxSwift 是否支持这样的东西?如果没有,那么我想我可以自己实现它,例如通过 ChartController 中的 bool 属性,或通过将订阅添加到单独的 disposeBag,我将处理它然后重新创建订阅。但如果这样做是好事呢?例如,当出现一些错误时,bool 解决方案可能容易处理不当,并且 dispose/recreate 可能会以某种方式代价高昂,并且可能是这种情况下不打算像这样使用 dispose。
有更好的做法来处理这种情况吗?正如我所说,我认为这个问题很常见,所以我希望有一个规范的解决方案:) 感谢您的任何回答,很抱歉这篇冗长的帖子。
【问题讨论】:
我认为现在 dispose/recreate 解决方案无论如何都行不通,因为(如果我没记错的话)再次订阅将立即触发订阅主体... 【参考方案1】:所以一个问题是,如果我解决问题的总体布局是可以的,还是应该以不同的方式处理?
一个正确编写的 UI 输入元素 observable 只会在 用户 对 UI 进行更改时触发,而不是在程序进行更改时触发。例如:
textField.rx.text.orEmpty.subscribe(onNext: print($0) )
只会在用户在 textField 中键入时打印一个值,而不是在您调用 textField.text = "foo"
或从绑定 .bind(to: textfield.rx.text)
时打印。
如果您编写了 ChartInfoController,我建议您修改它以使其与其他 UI 元素一样工作。如果不是你写的,请向开发者/维护者提交问题。
RxSwift 是否支持 [临时禁用特定订阅] 之类的功能?
这取决于您所说的“暂时禁用”是什么意思。它不支持静默取消订阅和重新订阅,但是有很多运算符会过滤掉他们收到的一些事件,同时传递其他事件。例如filter
、throttle
、debounce
、ignoreElements
...有很多人这样做。
有没有更好的做法来处理这种情况?
那么上面提到了最好的解决方案。
【讨论】:
OK,但是用户更改了选择的chartData,这最终需要更新 selectedXRange 属性。它是 ChartInfoController 中的一个字段,当它发生变化时,它必须更新一些订阅它的东西。因此,如果新数据到达并且更改 selectedXRange 不会触发它的订阅,那么很多 ChartInfoController 元素将不会更新。所以我不确定我是否完全按照你的回答:/ 您能否详细说明“否,并且试图这样做违背了图书馆的精神,并破坏了它的声明性/不变性。”为什么基于某些属性暂时禁用订阅是违背精神的,但另一方面却允许限制?根据“传递的时间”参数,它完全是临时禁用订阅。此外,这个时间参数是不确定的,与我的相反。【参考方案2】:当我们对同一个 Observable 有多个订阅时,它将为每个订阅重新执行。
停止为每个订阅重新执行。 RxSwift 有几个运算符:share()
、replay()
、replayAll()
、shareReplay()
、publish()
,甚至是shareReplayLatestWhileConnected()
。
在 (RxSwift: share vs replay vs shareReplay) 了解更多信息
【讨论】:
以上是关于防止 RxSwift 中的冗余操作的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 RxSwift disposeBag 防止 UITableViewCell 中重复的 UIButton 点击
根据 UITextField 中的值禁用按钮只能工作一次(RxSwift)
RxSwift - 在 UISearchBar 中以编程方式触发搜索