假设我们有以下用 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)")
Run Code Online (Sandbox Code Playgroud)
此代码在控制台上打印以下结果(可以在 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
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,sink 方法的代码在 willSet 之后和给定属性的 didSet 之前执行。现在我的问题是:有没有什么方法可以创建这个发布者或订阅它,以便给接收器的代码在 didSet 之后执行,而不是在它之前执行(这样,当执行从接收器打印时, arrayCount 和 valueCount 将是相同的)在上面的例子中)?
Published.Publisher用于willSet发出包装属性的值。不幸的是,您无法更改此行为,唯一的解决方案是实现您自己的属性包装器,didSet而不是使用willSet.
您还可以使用输入参数自定义是否希望在订阅发布projectValue者时接收当前值(匹配@Published行为)emitCurrentValue。如果设置为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()
}
}
Run Code Online (Sandbox Code Playgroud)
您可以像观察 a@PostPublished一样观察 a @Published。
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
Run Code Online (Sandbox Code Playgroud)
首先,如果目的是让发布者在 didSet 上发出事件,那么您可以简单地使用CurrentValueSubject. 它的语法不像 那样漂亮@Published,但它确实有效:
var array = CurrentValueSubject<[Int], Never>([])
// ...
let second = array.value[1]
// ...
array.sink {
print("array count: $0.count")
}
array.send([1, 2, 3])
Run Code Online (Sandbox Code Playgroud)
如果您已经有一个@Published希望保留的变量(例如,您的 SwiftUI 视图使用它),但您还希望在发生变化后观察变化,那么您可以添加 aCurrentValueSubject并更新它didSet
class Test {
@Published var array: [Int] = [] {
didSet {
arrayChangedEvent.send(array)
}
}
var arrayChangedEvent = CurrentValueSubject<[Int], Never>([])
}
Run Code Online (Sandbox Code Playgroud)
然后以几乎相同的方式观察数组:arrayChangedEvent.sink { /* ... */ }
小智 6
好吧,我会说使用一个简单的技巧
var test = Test()
var subscriber = test.$array
.receive(on: DispatchQueue.main)
.sink { values in
print("arrayCount: \(test.array.count) valuesCount: \(values.count)")
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6366 次 |
| 最近记录: |