I'm trying to implement something similar to @Published, only that it will also trigger objectWillChange.send() if the published value is itself also an ObservableObject. In order to do so, I need to dynamically access the published value's .objectWillChange in order to subscribe to it, and re-emit its events upstream.
Since ObservableObject is a PAT, a naïve casting won't do, and since the default implementation of .objectWillChange is implemented in a protocol extension, using Mirror to search for a publisher doesn't seem to work wither.
I think that it can - with some restriction:
You can add a property wrapper that knows enclosing self and restrict it to where you have an ObservableObject where it’s associated willChange publisher is an ObservableObjectPublisher. Because that one has a ‘send’ method.
So it works in case you do not add a custom willChange publisher. Just like @Published.
Not at a computer, so I can’t verify the details, but something like that.
Sorry, I misread that you are trying to get at the wrapped value’s objectWillChange… I can’t directly tell if generic restrictions could help in that case…
Add an init(wrappedValue:) overload in an extension that requires Value: ObservableObject:
import Combine
@propertyWrapper
public struct ObservablePublished<Value> {
private let valueWillChange: AnyPublisher<Void, Never>
private let value: Value
public init(wrappedValue: Value) {
print("non-ObservableObject init with \(wrappedValue)")
self.value = wrappedValue
valueWillChange = Empty(completeImmediately: false).eraseToAnyPublisher()
}
@available(*, unavailable, message: "@Published is only available on properties of classes")
public var wrappedValue: Value {
get { fatalError() }
set { fatalError() }
}
public static subscript<EnclosingSelf>(_enclosingInstance object: EnclosingSelf, wrapped wrappedKeyPath: Swift.ReferenceWritableKeyPath<EnclosingSelf, Value>, storage storageKeyPath: Swift.ReferenceWritableKeyPath<EnclosingSelf, ObservablePublished<Value>>) -> Value where EnclosingSelf : AnyObject {
get { object[keyPath: storageKeyPath].value }
set { }
}
}
extension ObservablePublished where Value: ObservableObject {
public init(wrappedValue: Value) {
print("ObservableObject init with \(wrappedValue)")
self.value = wrappedValue
valueWillChange = wrappedValue.objectWillChange.map { _ in () }.eraseToAnyPublisher()
}
}
class MyInnerObservable: ObservableObject { }
class MyOuterObservable: ObservableObject {
@ObservablePublished
var nonObservable: Int = 123
@ObservablePublished
var observable: MyInnerObservable = .init()
}
let outer = MyOuterObservable()