前言:这是关于反应式编程的设计问题。它旨在与语言无关,因此都是伪代码。我怀疑无论正确的答案是什么,在Rx / ReactiveCocoa / Combine之间同样适用。
我看到了3种设计观察对象的不同方式。每个人肯定都有优点/缺点,但是不清楚它们是什么。
您的对象可能具有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>
通过通知其他对象直接访问该对象,您可以简单地自己获取该对象的值。如果需要访问x
或y
值的流,则也只需一个map
调用即可。
但是,似乎有一些问题。
(Point, Publisher<Point>)
对,这似乎很麻烦。它可能有正确性问题。例如,在触发didChange
事件与访问该对象之间存在任何延迟,难道您读取的对象值可能比更改被触发时出现的值新吗?
[这种方法是我最不喜欢的方法,但是有趣的是,这是Apple在BindableObject
协议中采用其Combine框架的方法。我怀疑这可能与某种性能提升有关,因为在不需要的情况下不必在对象周围进行管道传输。是这样吗?
最明显的方法是流对象本身。 var didChange: Publisher<Point> { /* a publisher that emits self over time */ }
。在解决我列出的3个问题的同时,这似乎达到了方法1的相同。我看不到方法1可以提供的任何价值。
您可以为对象的每个字段创建发布者:
class Point: BindableObject {
let x = PublishSubject<Int>
let y = PublishSubject<Int>
}
这是更详细的信息,因此人们可以仅将自己所关注的领域归为自己。我不知道重量级订阅的情况如何,但仅更具体地订阅您关心的内容,可能会获得一些性能上的收益。这里有一个人为的例子,因为很难想到只需要知道一个点的x值或y值的情况。但是该原理仍然普遍适用。
使用前两种方法之一访问xs和ys也是可能的,方法是将流映射到xs并进行重复数据删除(.map { $0.x }.distinct
)。但这比像这样的直接订阅调用了更多的映射闭包,这可能会影响性能。
此方法也可以与方法1或2结合使用,以在需要观察整个点对象时添加类型为var didChange
或Publisher<()>
的Publisher<Point>
属性。
这曾经导致很多API膨胀。在Rx中:
x: Value<Int>
,并在各处使用x.value
访问当前值或者,您有var xObservable: Value<Int>
和var x: Int { xObservable.value }
,但这增加了很多API膨胀。
幸运的是,属性包装器通过基本实现后一种设计,而又无需显式添加所有计算出的属性(它们是为您生成的),就解决了这两个问题。
您能否就使用哪种模式提供一些指导?我怀疑那是“经验丰富”的东西之一,但是在反应式编程方面我还不存在。谢谢!
前言:这是关于反应式编程的设计问题。它旨在与语言无关,因此都是伪代码。我怀疑,无论正确的答案是什么,都会同样适用...
这个问题有很多需要考虑的地方,但我可能会说要选择选项2。