观察didSet之后Swift合并中@Published var的变化吗?

14ifxucb  于 2023-05-21  发布在  Swift
关注(0)|答案(3)|浏览(120)

假设我们有一个用Swift编写的代码,它使用合并:

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之后执行而不是在didSet之前执行(这样在上面的示例中执行print from sink时arrayCount和valuesCount将是相同的)?

czfnxgou

czfnxgou1#

Published.Publisher使用willSet发出 Package 属性的值。不幸的是,您无法改变这种行为,唯一的解决方案是实现您自己的属性 Package 器,使用didSet而不是willSet
您还可以使用emitCurrentValue输入参数自定义是否希望在订阅projectValue发布者时接收当前值(匹配@Published行为)。如果设置为true,则当前wrappedValue将在订阅时发送给新订阅者。

/// 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.
@available(iOS 13, *)
@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: any Subject<Value, Never>
  public var wrappedValue: Value {
    didSet {
      didChangeSubject.send(wrappedValue)
    }
  }

  /// - parameter emitCurrentValue: whether to emit the current wrapped value when subscribing to `projectValue`
  public init(wrappedValue: Value, emitCurrentValue: Bool = false) {
    self.wrappedValue = wrappedValue
    let didChangeSubject: any Subject<Value, Never>
    if emitCurrentValue {
      didChangeSubject = CurrentValueSubject(wrappedValue)
    } else {
      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

class UsingPostPublished {
  @PostPublished var dontEmitInitial = 0
  @PostPublished(emitCurrentValue: true) var emitInitial = 0
}

private var cancellables = Set<AnyCancellable>()

let usingPostPublished = UsingPostPublished()
usingPostPublished.$dontEmitInitial.sink {
  print("dontEmitInitial did change to \($0)")
}.store(in: &cancellables)

usingPostPublished.$emitInitial.sink {
  print("emitInitial did change to \($0)")
}.store(in: &cancellables)

usingPostPublished.emitInitial = 1
usingPostPublished.dontEmitInitial = 1
but5z9lq

but5z9lq2#

首先,如果目的是让发布者在didSet上发出事件,那么您可以简单地使用CurrentValueSubject。它的语法不像@Published那么漂亮,但它确实做到了这一点:

var array = CurrentValueSubjet<[Int], Never>([])
// ...
let second = array.value[1]
// ...
array.sink {
    print("array count: $0.count")
}
array.send([1, 2, 3])

如果您已经有一个想要保留的@Published变量(例如,您的SwiftUI视图使用了它),但您还希望在它们发生后观察更改,那么您可以添加一个CurrentValueSubject并在didSet中更新它

class Test {
  @Published var array: [Int] = [] {
    didSet {
        arrayChangedEvent.send(array)
    }
  }
    var arrayChangedEvent = CurrentValueSubject<[Int], Never>([])
}

然后以大致相同的方式观察数组:arrayChangedEvent.sink { /* ... */ }

jjjwad0x

jjjwad0x3#

我会说用一个简单的黑客

var test = Test()
var subscriber = test.$array
.receive(on: DispatchQueue.main)
.sink { values in
print("arrayCount: \(test.array.count) valuesCount: \(values.count)")
}

相关问题