一个不真正使用反应性可可的案例

在生产项目中,我已经在生产项目中使用了功能性反应式编程(FRP)和反应性可可粉(以及最近的反应性Swift),并且看到有人采用它。 在整个过程中,我们不仅学会了如何使用FRP,还学会了如何使用它。 在本文中,我将反思我自己犯下的一些典型的新手失误,以便来自传统命令式或混合语言(如Objective-C,Java或C#)背景的人也许可以从我的错误中学到东西。

我们将考虑一个问题以及三个不同的解决方案:

  1. 天真的(和错误的)命令式解决方案,
  2. 正确的命令,以及
  3. 正确的FRP样式的解决方案

希望最后,您将了解FRP如何有助于解决与UI和异步编程有关的一些问题。

本文假定您熟悉Reactive Swift的基础知识(尤其是Signals和SignalProducers的概念)。 您可以使用ReactiveSwift自述文件快速赶上那些。

问题

假设我们开发了一个带有视图控制器的购物应用程序,该控制器需要显示可从远程API获得的产品描述列表。 有文字说明和图片网址。 该图像将必须使用网络请求进行下载。 假设网络请求是使用Reactive Swift的SignalProducer提取的,我们将专注于以MVVM方式为表视图单元格编写代码。

天真的解决方案

事实证明,这不是我们使用此代码可以获得的唯一问题。 考虑下面的序列图,其中下载发生的速度比滚动使单元1离开屏幕的速度更快。 当重复使用单元格中的产品a的图像替换为正确的产品b的图像时,我们将再次观察到图像的错误归因。

让我们来解决它!

我们是强大的! 我们可以立即修复所有问题! (实际上,这将需要一些时间,但是请注意!)当单元格收到新的viewModel值时,我们将终止任何先前的下载信号订阅。 这样,我们将解决序列1的问题。 然后,对于每个新的viewModel值,我们将使用占位符(在本例中为nil )替换单元格中的图像,以防止显示的文本内容与图像之间的不匹配,如序列2中所述 。 听起来像是个计划?

使用这些修复程序后,单元将仅显示与分配的产品或占位符相对应的图像。 我们终于都准备好了,不是吗?—看这段代码,让我们问问自己,为什么我们这里甚至使用了活性可可粉? 我们从学习和使用新图书馆中获得了哪些好处?

  1. 可变属性! —并非如此,我们可以在这里使用内置的Swift的didSet观察器。
  2. 信号产生器在此处封装了异步操作的状态! —是的,但是众所周知的Foundation的Operation对象也是如此。 我们可以定义一个具有依赖BlockOperationDownloadImageOperation ,并且甚至不需要学习Reactive Cocoa就可以完成该任务。

我们可以做得更好吗? —是的。 这里发生的事情是,我们使用FRP的方式与使用常规命令式工具的方式相同,而没有考虑该方法对于在此处理的UI和异步编程问题必须提供的功能。

玻璃钢解决方案

函数式反应式编程可让我们使用(并为方便起见而使用)的两个出色工具是:

  1. UI编程的绑定
  2. 将信号与功能样式运算符(例如贴图,过滤器和展平器)结合使用,并自然地管理所得信号的寿命。

这是我们如何将其应用于问题的方法(为清楚起见,我们将只专注于awakeFromNib的实现(其余原始代码将保持不变):

让我们确切说明我们在这里做什么。

  1. <~是一个绑定运算符,作用于实现BindingTargetProtocol (左侧)和BindingSourceProtocol (右侧)的值。 将运算符应用于这些值会在目标指定的生存期内(在我们的示例中,这是UILabelUIImageView的生存期)将目标与源发出的值进行订阅,即,保证订阅将在指定的生存时间内终止。
  2. 请注意,在第3行和第4行中,我们如何直接映射属性而不是映射信号生成器。 属性的此功能仅可从Reactive Swift 1开始用于此目的,尽管在技术上可以在Reactive Cocoa 4中映射属性,但临时属性对象的生存期仅允许绑定从源中获取当前值属性。
  3. awakeFromNib函数的最后一行中,我们使用flatMap(.latest)运算符将视图模型的信号生成器转换为图像的信号生成器。 与我们初次尝试解决问题时使用的startWithValues调用相比,有什么好处? —在后台,它确实完成了我们固定解决方案的工作:它处置了与最新viewModel值不对应的任何下载信号订阅,订阅状态封装在操作员的实现中。 这处理了序列1中描述的边缘情况。
  4. 我们在每个新的图像下载信号前添加一个nil值,以便隐藏视图中先前存在的任何图像并避免序列2中描述的情况人们可以将此值视为在信号开始后立即到达的占位符图像。

因此,我们只用了几行声明性代码就得出了解决问题的正确方法,而不必担心应用程序中的特定事件序列-我们使用绑定指定需要获取的内容,而不是逐步获取的方式和功能样式运算符。 这就是FRP旨在促进的。

    Interesting Posts