I found a lot of SwiftUI-related topics about this which didn't help (eg Why an ObservedObject array is not updated in my SwiftUI application?)
This doesn't work with Combine in Swift (specifically not using SwiftUI):
class SomeTask {
@Published var progress = Progress(totalUnitCount: 5) // Progress is a Class
[...]
}
var task = SomeTask()
let cancellable = task.$progress.sink { print($0.fractionCompleted) }
task.progress.completedUnitCount = 2
This is not SwiftUI-related so no ObservableObject
inheritance to get objectWillChange
, but even if I try to use ObservableObject
and task.objectWillChange.send()
it doesn't do anything, also trying to add extension Progress: ObservableObject {}
doesn't help.
Since the publisher emits values through the var's willSet
and since Progress
is itself class-type nothing happens.
Looks like there is no real decent way to manually trigger it?
Only solution I found is to just re-assign itself which is quite awkward:
let pr = progress
progress = pr
(writing progress = progress
is a compile-time error).
Only other way which might be working is probably by using Key-value-observing/KVO and/or writing a new @PublishedClassType
property wrapper?
I was able to implement this using KVO, wrapped by a @propertyWrapper
, with a CurrentValueSubject
as the publisher:
@propertyWrapper
class PublishedClass<T : NSObject> {
private let subject: CurrentValueSubject<T, Never>
private var observation: NSKeyValueObservation? = nil
init<U>(wrappedValue: T, keyPath: ReferenceWritableKeyPath<T, U>) {
self.wrappedValue = wrappedValue
subject = CurrentValueSubject(wrappedValue)
observation = wrappedValue.observe(keyPath, options: [.new]) { (wrapped, change) in
self.subject.send(wrapped)
}
}
var wrappedValue: T
var projectedValue: CurrentValueSubject<T, Never> {
subject
}
deinit {
observation.invalidate()
}
}
Usage:
class Bar : NSObject {
@objc dynamic var a: Int
init(a: Int) {
self.a = a
}
}
class Foo {
@PublishedClass(keyPath: \.a)
var bar = Bar(a: 0)
}
let f = Foo()
let c = f.$bar.sink(receiveValue: { x in print(x.a) })
f.bar.a = 2
f.bar.a = 3
f.bar.a = 4
Output:
0
2
3
4
The disadvantage of using KVO is, of course, that the key path you pass in must be @objc dynamic
and the root of the keypath must be an NSObject
subclass. :(
I haven't tried, but it should be possible to extend this to observe on multiple key paths if you want.