2

我正在考虑使用我自己的自定义信号框架将项目转换为使用 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
}

稍后,我们收到一些信息,导致我们同时更新两者的ab

a.value = 3
b.value = 4

现在的问题是它c会通知它的听众它有这些值3 -> 5 -> 7。5 完全是虚假的,并不代表有效状态,因为我们从不想要一个a等于 3 和b等于 2 的状态。

有没有解决的办法?一种抑制更新的方法,Property同时将其所有依赖项更新为新状态,并且仅在完成后才允许更新?

4

1 回答 1

4

combineLatest的基本目的是在其上游输入中的任何一个发送新值时发送一个值,因此如果您想使用该运算符,我认为没有办法避免这个问题。

如果两个值真正同时更新很重要,则考虑使用 aMutableProperty<(Int, Int)>或将两个值放入结构中。如果您提供更多有关您实际尝试完成的内容的背景信息,那么也许我们可以提供更好的答案。

暂停更新

所以我真的不建议做这样的事情,但如果你想要一种通用技术来“暂停”更新,那么你可以使用一个全局变量来指示更新是否暂停,并且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)

但是可能有更好的特定于上下文的解决方案来实现您想要实现的任何目标。

使用采样器手动触发更新

另一种解决方案是使用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 { $0.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 { $0.a + $0.b }

c.producer.startWithValues { c in print(c) }

ab.modify { values in
    values.a = 3
    values.b = 4
}

你甚至可以拥有直接访问的便利属性ab即使该ab属性是事实的来源:

let a = ab.map(\.a)
let b = ab.map(\.b)

创建一种新类型的可变属性来包装复合属性

您可以创建一个符合以下条件的新类,MutablePropertyProtocol以使其更符合人体工程学地使用结构来保存您的值:

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
    }
}

有了这个,您可以创建 和 的可变版本,ab使得获取和设置值变得既好又容易:

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 { $0.a + $0.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
于 2019-06-26T23:42:51.910 回答