设计可观察对象

时间:2019-11-07 00:09:27

标签: swift reactive-programming rx-swift reactive-cocoa combine

前言:这是有关反应式编程的设计问题。它旨在与语言无关,因此都是伪代码。我怀疑无论正确的答案是什么,在Rx / ReactiveCocoa / Combine之间同样适用。

我看到了3种设计观察对象的不同方法。每个人肯定都有优点/缺点,但不清楚它们是什么。

  1. 您的对象可以具有didChange: Publisher<()>属性。订阅时,您会收到有关发布对象何时更改的通知,但实际上您并未了解有关更改或模型对象的新值的任何信息。这很容易用map来解决:

    class Point: BindableObject {
        var x, y: Int
        var didChange: Publisher<()> { ... }
    }
    let object: Point = ...
    let streamOfPoints = object.didChange //  Publisher<()>
                                .map { _ in object }  // Publisher<Point>
    

    通过通知其他对象直接访问该对象,您可以简单地自己获取该对象的值。如果您需要访问xy值的流,则也只需一个map调用即可。

    但是,这似乎有一些问题。

    1. 这是一个额外的步骤
    2. 这需要您有权访问原始对象,因此仅使发布者四处转移是不够的。您必须绕过一对(Point, Publisher<Point>),这似乎很麻烦。
    3. 它可能存在正确性问题。例如,在触发didChange事件与访问对象之间的任何延迟,都可能读取的对象值比触发更改时出现的值新。关闭?

      此方法是我最不喜欢的方法,但有趣的是,这是Apple在其BindableObject协议的Combine框架中采用的方法。我怀疑这可能与某种性能提升有关,因为在不需要的情况下不必在对象周围进行管道传输。是这样吗?

  2. 最明显的方法是流式传输对象本身。 var didChange: Publisher<Point> { /* a publisher that emits self over time */ }。在解决我列出的3个问题的同时,这似乎达到了方法1的相同。我看不到方法1可以提供的任何价值。

  3. 您可以为对象的每个字段创建发布者:

    class Point: BindableObject {
        let x = PublishSubject<Int>
        let y = PublishSubject<Int>
    }
    

    这是更详细的信息,因此人们可以将自己仅订阅他们关心的领域。我不知道重量级订阅的情况如何,但仅更具体地订阅您关心的内容,可能会获得一些性能上的收益。这里有一个人为的例子,因为很难想到只需要知道一个点的x值或y值的情况。但是该原则仍然普遍适用。

    通过将流映射到xs并进行重复数据删除(.map { $0.x }.distinct),也可以使用前两种方法之一访问xs和ys。但这需要比直接订阅更多的映射闭包,这可能会影响性能。

    此方法也可以与方法1或2结合使用,以在需要观察整个点对象时添加类型var didChangePublisher<()>的{​​{1}}属性。 / p>

    这曾经导致很多API膨胀。在Rx中:

    1. 您可以拥有Publisher<Point>,然后在各处使用x: Value<Int>来访问当前值
    2. 或者,您有x.valuevar xObservable: Value<Int>,但这增加了很多API膨胀。

      幸运的是,属性包装器通过基本实现后一种设计解决了这两个问题,而无需显式添加所有计算出的属性(它们是为您生成的)。

能否请您提供使用哪种模式的指导?我怀疑那是“经验丰富”的东西之一,但是在反应式编程方面我还不存在。谢谢!

1 个答案:

答案 0 :(得分:1)

这个问题有很多需要考虑的地方,但我可能会说选择2。

我不是设计响应式框架的专家,但是从您的选择来看,我们可以消除第一个框架,因为它太局限了,无法使用,从而留下了选项2和3。

如您所提到的,选项3允许很多粒度,但是我认为,对于常规用例,这种粒度会导致更大的膨胀-例如我对整个模型发生了变化很感兴趣,并且使用.map.filter可以轻松地将选项2中的流减少到需要时轻松地返回到细粒度选项。在我看来,更常见的情况是查看正在观察的整个模型,而不是其中的一部分。

当前,选项2在Rx和类似框架的设计中似乎也很普遍,我认为这只是经验丰富的解决方法。

希望这会有所帮助,似乎这更适合讨论板,而不是简单的答案。