Hi all,
I have a fairly large code base that is using RxSwift quite a lot. (RxSwift is similar to Combine for those who are not familiar with it.)
We have been replacing a lot of view models with @Observable types the past years, and for a large part the refactor has been trivial.
But recently I found a side effect of using RxSwift that I had not fully appreciated before.
In RxSwift it's very easy to 'focus in' on a nested child property of a changing value and only get signal updates for the changes to the nested child property that you care about.
Let's say that you have a type, BigStruct:
struct BigStruct {
var nested: [String: Int] = [:]
var value: String = ""
}
I know it's not that big, but it serves the purpose. ![]()
Let's say that you have a source of BigStructs where nested change frequently but value does not.
With Rx you could have a view model like:
class ViewModel {
let bigStructs: Observable<BigStruct>
lazy var values: Observable<String> = bigStructs.map(\.value).distinctUntilChanged()
}
Now the 'values' is focused on a part of the BigStruct that changes more rarely than the entire value. When bound to a UI component, the component will only update on value changes.
I did not find an easy way to translate that to a corresponding @Observable view model.
And that of course got me wondering: is this a common pattern? Is my code base special in having the need for such a concept?
I don't recall having seen any discussions about anything similar.
I am currently experimenting with a macro: @ObservationDerived.
It should expand to something similar to):
var value: String { _derived.value }
@ObservationIgnored
lazy var _derived: Derived<String, ViewModel> =
Derived(source: self, transform: \.bigStruct.value)
Where Derived wraps a stored property (a cache so to say) and uses withObservationTracking to get updates.
This will naturally cause observation of the root value even though you may not be observing the derived value on every code path of the view, so it always does a bit extra work.
I found many use cases for this for generating calculated values that are derived from multiple entities and properties representing user choices from the UI.
Has anyone else had the need for such a pattern?
And if not, I am wondering why not? ![]()
Sincerely,
Morten