在 ReactiveSwift 中同步组合属性
Synchronising combined Properties in ReactiveSwift
我正在考虑将使用我自己的自定义信号框架的项目转换为使用 ReactiveSwift,但是有一个基本问题我一直不知道如何在 ReactiveSwift 中解决:
作为一个简化示例,假设您有两个可变属性:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
然后,我们推导出一个 属性 结合两者来实现我们的逻辑:
let c = Property.combineLatest(a, b).map { a, b in
return a + b
}
稍后,我们收到一些信息,导致我们同时更新 a
和 b
的值:
a.value = 3
b.value = 4
现在的问题是 c
会通知它的听众它有值 3 -> 5 -> 7
。 5 完全是虚假的,并不代表有效状态,因为我们从来不想要 a
等于 3 而 b
等于 2 的状态。
有办法解决这个问题吗?一种在将其所有依赖项更新为新状态时抑制对 Property
的更新,并且仅在完成后才允许更新通过的方法?
combineLatest
的基本目的是在其任一上游输入发送新值时发送一个值,因此如果您想使用该运算符,我认为没有办法避免此问题.
如果两个值真正同时更新很重要,那么请考虑使用 MutableProperty<(Int, Int)>
或将两个值放在一个结构中。如果您提供更多关于您实际想要完成的事情的背景,那么也许我们可以给出更好的答案。
暂停更新
所以我真的不建议做这样的事情,但是如果你想要一个用于 "pausing" 更新的通用技术,那么你可以使用一个全局变量来指示更新是否暂停和 filter
运算符:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
var pauseUpdates = false
let c = Property.combineLatest(a, b)
.filter(initial: (0, 0)) { _ in !pauseUpdates }
.map { a, b in
return a + b
}
func update(newA: Int, newB: Int) {
pauseUpdates = true
a.value = newA
pauseUpdates = false
b.value = newB
}
c.producer.startWithValues { c in print(c) }
update(newA: 3, newB: 4)
但可能有更好的 context-specific 解决方案来实现您想要实现的目标。
使用采样器手动触发更新
另一种解决方案是使用 sample
运算符手动选择何时取值:
class MyClass {
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c: Property<Int>
private let sampler: Signal<Void, Never>.Observer
init() {
let (signal, input) = Signal<Void, Never>.pipe()
sampler = input
let updates = Property.combineLatest(a, b)
.map { a, b in
return a + b
}
.producer
.sample(with: signal)
.map { [=11=].0 }
c = Property(initial: a.value + b.value, then: updates)
}
func update(a: Int, b: Int) {
self.a.value = a
self.b.value = b
sampler.send(value: ())
}
}
let x = MyClass()
x.c.producer.startWithValues { c in print(c) }
x.update(a: 3, b: 4)
使用zip
如果 a
和 b
总是一起改变,您可以使用 zip
运算符等待两个输入都有新值:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c = Property.zip(a, b).map(+)
c.producer.startWithValues { c in print(c) }
a.value = 3
b.value = 4
将zip
与每种更新类型的方法一起使用
class MyClass {
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c: Property<Int>
init() {
c = Property.zip(a, b).map(+)
}
func update(a: Int, b: Int) {
self.a.value = a
self.b.value = b
}
func update(a: Int) {
self.a.value = a
self.b.value = self.b.value
}
func update(b: Int) {
self.a.value = self.a.value
self.b.value = b
}
}
let x = MyClass()
x.c.producer.startWithValues { c in print(c) }
x.update(a: 5)
x.update(b: 7)
x.update(a: 8, b: 8)
将值组合成一个结构
我想我会提供一个这样的例子,尽管你说你不想这样做,因为 MutableProperty
有一个 modify
方法可以使它没有你想象的那么麻烦进行原子更新:
struct Values {
var a: Int
var b: Int
}
let ab = MutableProperty(Values(a: 1, b: 2))
let c = ab.map { [=14=].a + [=14=].b }
c.producer.startWithValues { c in print(c) }
ab.modify { values in
values.a = 3
values.b = 4
}
您甚至可以拥有直接访问 a
和 b
的便捷属性,即使 ab
属性 是真实的来源:
let a = ab.map(\.a)
let b = ab.map(\.b)
正在创建一个新的可变类型 属性 来包装复合 属性
您可以创建一个符合 MutablePropertyProtocol
的新 class 以使其更符合人体工程学地使用结构来保存您的值:
class MutablePropertyWrapper<T, U>: MutablePropertyProtocol {
typealias Value = U
var value: U {
get { property.value[keyPath: keyPath] }
set {
property.modify { val in
var newVal = val
newVal[keyPath: self.keyPath] = newValue
val = newVal
}
}
}
var lifetime: Lifetime {
property.lifetime
}
var producer: SignalProducer<U, Never> {
property.map(keyPath).producer
}
var signal: Signal<U, Never> {
property.map(keyPath).signal
}
private let property: MutableProperty<T>
private let keyPath: WritableKeyPath<T, U>
init(_ property: MutableProperty<T>, keyPath: WritableKeyPath<T, U>) {
self.property = property
self.keyPath = keyPath
}
}
有了这个,您可以创建 a
和 b
的可变版本,这使得获取和设置值变得既好又容易:
struct Values {
var a: Int
var b: Int
}
let ab = MutableProperty(Values(a: 1, b: 2))
let a = MutablePropertyWrapper(ab, keyPath: \.a)
let b = MutablePropertyWrapper(ab, keyPath: \.b)
let c = ab.map { [=17=].a + [=17=].b }
c.producer.startWithValues { c in print(c) }
// Update the values individually, triggering two updates
a.value = 10
b.value = 20
// Update both values atomically, triggering a single update
ab.modify { values in
values.a = 30
values.b = 40
}
如果您安装了 Xcode 11 Beta,您甚至可以使用基于新键路径的 @dynamicMemberLookup
功能来使其更符合人体工程学:
@dynamicMemberLookup
protocol MemberAccessingProperty: MutablePropertyProtocol {
subscript<U>(dynamicMember keyPath: WritableKeyPath<Value, U>) -> MutablePropertyWrapper<Value, U> { get }
}
extension MutableProperty: MemberAccessingProperty {
subscript<U>(dynamicMember keyPath: WritableKeyPath<Value, U>) -> MutablePropertyWrapper<Value, U> {
return MutablePropertyWrapper(self, keyPath: keyPath)
}
}
现在代替:
let a = MutablePropertyWrapper(ab, keyPath: \.a)
let b = MutablePropertyWrapper(ab, keyPath: \.b)
你可以这样写:
let a = ab.a
let b = ab.b
或者直接设置值而不创建单独的变量:
ab.a.value = 10
ab.b.value = 20
我正在考虑将使用我自己的自定义信号框架的项目转换为使用 ReactiveSwift,但是有一个基本问题我一直不知道如何在 ReactiveSwift 中解决:
作为一个简化示例,假设您有两个可变属性:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
然后,我们推导出一个 属性 结合两者来实现我们的逻辑:
let c = Property.combineLatest(a, b).map { a, b in
return a + b
}
稍后,我们收到一些信息,导致我们同时更新 a
和 b
的值:
a.value = 3
b.value = 4
现在的问题是 c
会通知它的听众它有值 3 -> 5 -> 7
。 5 完全是虚假的,并不代表有效状态,因为我们从来不想要 a
等于 3 而 b
等于 2 的状态。
有办法解决这个问题吗?一种在将其所有依赖项更新为新状态时抑制对 Property
的更新,并且仅在完成后才允许更新通过的方法?
combineLatest
的基本目的是在其任一上游输入发送新值时发送一个值,因此如果您想使用该运算符,我认为没有办法避免此问题.
如果两个值真正同时更新很重要,那么请考虑使用 MutableProperty<(Int, Int)>
或将两个值放在一个结构中。如果您提供更多关于您实际想要完成的事情的背景,那么也许我们可以给出更好的答案。
暂停更新
所以我真的不建议做这样的事情,但是如果你想要一个用于 "pausing" 更新的通用技术,那么你可以使用一个全局变量来指示更新是否暂停和 filter
运算符:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
var pauseUpdates = false
let c = Property.combineLatest(a, b)
.filter(initial: (0, 0)) { _ in !pauseUpdates }
.map { a, b in
return a + b
}
func update(newA: Int, newB: Int) {
pauseUpdates = true
a.value = newA
pauseUpdates = false
b.value = newB
}
c.producer.startWithValues { c in print(c) }
update(newA: 3, newB: 4)
但可能有更好的 context-specific 解决方案来实现您想要实现的目标。
使用采样器手动触发更新
另一种解决方案是使用 sample
运算符手动选择何时取值:
class MyClass {
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c: Property<Int>
private let sampler: Signal<Void, Never>.Observer
init() {
let (signal, input) = Signal<Void, Never>.pipe()
sampler = input
let updates = Property.combineLatest(a, b)
.map { a, b in
return a + b
}
.producer
.sample(with: signal)
.map { [=11=].0 }
c = Property(initial: a.value + b.value, then: updates)
}
func update(a: Int, b: Int) {
self.a.value = a
self.b.value = b
sampler.send(value: ())
}
}
let x = MyClass()
x.c.producer.startWithValues { c in print(c) }
x.update(a: 3, b: 4)
使用zip
如果 a
和 b
总是一起改变,您可以使用 zip
运算符等待两个输入都有新值:
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c = Property.zip(a, b).map(+)
c.producer.startWithValues { c in print(c) }
a.value = 3
b.value = 4
将zip
与每种更新类型的方法一起使用
class MyClass {
let a = MutableProperty<Int>(1)
let b = MutableProperty<Int>(2)
let c: Property<Int>
init() {
c = Property.zip(a, b).map(+)
}
func update(a: Int, b: Int) {
self.a.value = a
self.b.value = b
}
func update(a: Int) {
self.a.value = a
self.b.value = self.b.value
}
func update(b: Int) {
self.a.value = self.a.value
self.b.value = b
}
}
let x = MyClass()
x.c.producer.startWithValues { c in print(c) }
x.update(a: 5)
x.update(b: 7)
x.update(a: 8, b: 8)
将值组合成一个结构
我想我会提供一个这样的例子,尽管你说你不想这样做,因为 MutableProperty
有一个 modify
方法可以使它没有你想象的那么麻烦进行原子更新:
struct Values {
var a: Int
var b: Int
}
let ab = MutableProperty(Values(a: 1, b: 2))
let c = ab.map { [=14=].a + [=14=].b }
c.producer.startWithValues { c in print(c) }
ab.modify { values in
values.a = 3
values.b = 4
}
您甚至可以拥有直接访问 a
和 b
的便捷属性,即使 ab
属性 是真实的来源:
let a = ab.map(\.a)
let b = ab.map(\.b)
正在创建一个新的可变类型 属性 来包装复合 属性
您可以创建一个符合 MutablePropertyProtocol
的新 class 以使其更符合人体工程学地使用结构来保存您的值:
class MutablePropertyWrapper<T, U>: MutablePropertyProtocol {
typealias Value = U
var value: U {
get { property.value[keyPath: keyPath] }
set {
property.modify { val in
var newVal = val
newVal[keyPath: self.keyPath] = newValue
val = newVal
}
}
}
var lifetime: Lifetime {
property.lifetime
}
var producer: SignalProducer<U, Never> {
property.map(keyPath).producer
}
var signal: Signal<U, Never> {
property.map(keyPath).signal
}
private let property: MutableProperty<T>
private let keyPath: WritableKeyPath<T, U>
init(_ property: MutableProperty<T>, keyPath: WritableKeyPath<T, U>) {
self.property = property
self.keyPath = keyPath
}
}
有了这个,您可以创建 a
和 b
的可变版本,这使得获取和设置值变得既好又容易:
struct Values {
var a: Int
var b: Int
}
let ab = MutableProperty(Values(a: 1, b: 2))
let a = MutablePropertyWrapper(ab, keyPath: \.a)
let b = MutablePropertyWrapper(ab, keyPath: \.b)
let c = ab.map { [=17=].a + [=17=].b }
c.producer.startWithValues { c in print(c) }
// Update the values individually, triggering two updates
a.value = 10
b.value = 20
// Update both values atomically, triggering a single update
ab.modify { values in
values.a = 30
values.b = 40
}
如果您安装了 Xcode 11 Beta,您甚至可以使用基于新键路径的 @dynamicMemberLookup
功能来使其更符合人体工程学:
@dynamicMemberLookup
protocol MemberAccessingProperty: MutablePropertyProtocol {
subscript<U>(dynamicMember keyPath: WritableKeyPath<Value, U>) -> MutablePropertyWrapper<Value, U> { get }
}
extension MutableProperty: MemberAccessingProperty {
subscript<U>(dynamicMember keyPath: WritableKeyPath<Value, U>) -> MutablePropertyWrapper<Value, U> {
return MutablePropertyWrapper(self, keyPath: keyPath)
}
}
现在代替:
let a = MutablePropertyWrapper(ab, keyPath: \.a)
let b = MutablePropertyWrapper(ab, keyPath: \.b)
你可以这样写:
let a = ab.a
let b = ab.b
或者直接设置值而不创建单独的变量:
ab.a.value = 10
ab.b.value = 20