在 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 { /* ... */ }