在 didSet 之后观察 Swift Combine 中 @Published var 的变化?
Observe change on a @Published var in Swift Combine after didSet?
假设我们在 Swift 中编写了以下使用 Combine 的代码:
import UIKit
import Combine
class Test {
@Published var array: [Int] = [] {
willSet {
print("willSet \(newValue.count)")
}
didSet {
print("didSet \(array.count)")
}
}
}
var test = Test()
var subscriber = test.$array.sink { values in
print("arrayCount: \(test.array.count) valuesCount: \(values.count)")
}
print("1 arrayCount \(test.array.count)")
test.array = [1, 2, 3]
print("2 arrayCount \(test.array.count)")
test.array = [1]
print("3 arrayCount \(test.array.count)")
此代码在控制台上打印以下结果(可以在 playground 中快速测试):
arrayCount: 0 valuesCount: 0
1 arrayCount 0
willSet 3
arrayCount: 0 valuesCount: 3
didSet 3
2 arrayCount 3
willSet 1
arrayCount: 3 valuesCount: 1
didSet 1
3 arrayCount 1
正如我们所看到的,给定 sink 方法的代码是在给定 属性 的 willSet 之后和 didSet 之前执行的。现在我的问题是:有没有什么方法可以创建这个发布者或订阅它,让给 sink 的代码在 didSet 之后执行,而不是在它之前执行(这样当执行 print from sink 时,arrayCount 和 valuesCount 会相同在上面的例子中)?
Published.Publisher
使用 willSet
为包装的 属性 发出值。不幸的是,您无法更改此行为,唯一的解决方案是实现您自己的 属性 包装器,该包装器使用 didSet
而不是 willSet
.
/// A type that publishes changes about its `wrappedValue` property _after_ the property has changed (using `didSet` semantics).
/// Reimplementation of `Combine.Published`, which uses `willSet` semantics.
@propertyWrapper
public class PostPublished<Value> {
/// A `Publisher` that emits the new value of `wrappedValue` _after it was_ mutated (using `didSet` semantics).
public let projectedValue: AnyPublisher<Value, Never>
/// A `Publisher` that fires whenever `wrappedValue` _was_ mutated. To access the new value of `wrappedValue`, access `wrappedValue` directly, this `Publisher` only signals a change, it doesn't contain the changed value.
public let valueDidChange: AnyPublisher<Void, Never>
private let didChangeSubject: PassthroughSubject<Value, Never>
public var wrappedValue: Value { didSet { didChangeSubject.send(wrappedValue) } }
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
let didChangeSubject = PassthroughSubject<Value, Never>()
self.didChangeSubject = didChangeSubject
self.projectedValue = didChangeSubject.eraseToAnyPublisher()
self.valueDidChange = didChangeSubject.voidPublisher()
}
}
public extension Publisher {
/// Maps the `Output` of its upstream to `Void` and type erases its upstream to `AnyPublisher`.
func voidPublisher() -> AnyPublisher<Void, Failure> {
map { _ in Void() }
.eraseToAnyPublisher()
}
}
您可以像观察 @Published
一样观察 @PostPublished
。
private class ModelWithPostPublished<Value> {
@PostPublished var value: Value
init(value: Value) {
self.value = value
}
}
ModelWithPostPublished(value: "original").$value.sink { print("value WAS set to \([=11=]) }.store(in: &subscriptions)
首先,如果目的是让发布者在 didSet 上发出事件,那么您可以简单地使用 CurrentValueSubject
。它的语法不如 @Published
漂亮,但它确实有效:
var array = CurrentValueSubjet<[Int], Never>([])
// ...
let second = array.value[1]
// ...
array.sink {
print("array count: [=10=].count")
}
array.send([1, 2, 3])
如果您已经有一个希望保留的 @Published
变量(例如,您的 SwiftUI 视图使用它),但您还希望在 [=25= 之后观察变化] 他们发生了,那么你可以添加一个 CurrentValueSubject
并在 didSet
中更新它
class Test {
@Published var array: [Int] = [] {
didSet {
arrayChangedEvent.send(array)
}
}
var arrayChangedEvent = CurrentValueSubject<[Int], Never>([])
}
然后以大致相同的方式观察数组:arrayChangedEvent.sink { /* ... */ }
假设我们在 Swift 中编写了以下使用 Combine 的代码:
import UIKit
import Combine
class Test {
@Published var array: [Int] = [] {
willSet {
print("willSet \(newValue.count)")
}
didSet {
print("didSet \(array.count)")
}
}
}
var test = Test()
var subscriber = test.$array.sink { values in
print("arrayCount: \(test.array.count) valuesCount: \(values.count)")
}
print("1 arrayCount \(test.array.count)")
test.array = [1, 2, 3]
print("2 arrayCount \(test.array.count)")
test.array = [1]
print("3 arrayCount \(test.array.count)")
此代码在控制台上打印以下结果(可以在 playground 中快速测试):
arrayCount: 0 valuesCount: 0
1 arrayCount 0
willSet 3
arrayCount: 0 valuesCount: 3
didSet 3
2 arrayCount 3
willSet 1
arrayCount: 3 valuesCount: 1
didSet 1
3 arrayCount 1
正如我们所看到的,给定 sink 方法的代码是在给定 属性 的 willSet 之后和 didSet 之前执行的。现在我的问题是:有没有什么方法可以创建这个发布者或订阅它,让给 sink 的代码在 didSet 之后执行,而不是在它之前执行(这样当执行 print from sink 时,arrayCount 和 valuesCount 会相同在上面的例子中)?
Published.Publisher
使用 willSet
为包装的 属性 发出值。不幸的是,您无法更改此行为,唯一的解决方案是实现您自己的 属性 包装器,该包装器使用 didSet
而不是 willSet
.
/// A type that publishes changes about its `wrappedValue` property _after_ the property has changed (using `didSet` semantics).
/// Reimplementation of `Combine.Published`, which uses `willSet` semantics.
@propertyWrapper
public class PostPublished<Value> {
/// A `Publisher` that emits the new value of `wrappedValue` _after it was_ mutated (using `didSet` semantics).
public let projectedValue: AnyPublisher<Value, Never>
/// A `Publisher` that fires whenever `wrappedValue` _was_ mutated. To access the new value of `wrappedValue`, access `wrappedValue` directly, this `Publisher` only signals a change, it doesn't contain the changed value.
public let valueDidChange: AnyPublisher<Void, Never>
private let didChangeSubject: PassthroughSubject<Value, Never>
public var wrappedValue: Value { didSet { didChangeSubject.send(wrappedValue) } }
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
let didChangeSubject = PassthroughSubject<Value, Never>()
self.didChangeSubject = didChangeSubject
self.projectedValue = didChangeSubject.eraseToAnyPublisher()
self.valueDidChange = didChangeSubject.voidPublisher()
}
}
public extension Publisher {
/// Maps the `Output` of its upstream to `Void` and type erases its upstream to `AnyPublisher`.
func voidPublisher() -> AnyPublisher<Void, Failure> {
map { _ in Void() }
.eraseToAnyPublisher()
}
}
您可以像观察 @Published
一样观察 @PostPublished
。
private class ModelWithPostPublished<Value> {
@PostPublished var value: Value
init(value: Value) {
self.value = value
}
}
ModelWithPostPublished(value: "original").$value.sink { print("value WAS set to \([=11=]) }.store(in: &subscriptions)
首先,如果目的是让发布者在 didSet 上发出事件,那么您可以简单地使用 CurrentValueSubject
。它的语法不如 @Published
漂亮,但它确实有效:
var array = CurrentValueSubjet<[Int], Never>([])
// ...
let second = array.value[1]
// ...
array.sink {
print("array count: [=10=].count")
}
array.send([1, 2, 3])
如果您已经有一个希望保留的 @Published
变量(例如,您的 SwiftUI 视图使用它),但您还希望在 [=25= 之后观察变化] 他们发生了,那么你可以添加一个 CurrentValueSubject
并在 didSet
class Test {
@Published var array: [Int] = [] {
didSet {
arrayChangedEvent.send(array)
}
}
var arrayChangedEvent = CurrentValueSubject<[Int], Never>([])
}
然后以大致相同的方式观察数组:arrayChangedEvent.sink { /* ... */ }