Unexpected views reload when using @ObservableState - performance problems. #3656
Replies: 2 comments 3 replies
-
Hi @kacpermazurkiewicz, the behavior you are seeing is just how the observation tools of Swift work. It currently only detects mutations to a field, even if the mutation does not change the value, i.e. even However, there have been changes to observation that de-dupe change notifications (see swiftlang/swift#78151), but I don't believe those changes made it to Swift 6.1. But once those tools officially land we will definitely support them. But, unfortunately, even with those de-duping changes it will be possible to have the view re-render even when the data does not change. Take for instance this: @ObservableState
struct State {
var count = 0
var isEven: Bool { count.isMultiple(of: 2) }
func increment2() { count += 2 }
} Each call to Since this isn't an issue with the library I am going to convert it to a discussion. Please feel free to continue the conversation over there! |
Beta Was this translation helpful? Give feedback.
-
I think it’s also important to understand what “re-render” really means here. SwiftUI doesn’t just blindly re-render the entire view hierarchy. It detects that an observed property within its view body has changed so it will re-evaluate the entire view body. This does not mean it will re-render everything - all of the child view values will be recreated (i.e. new values will be initialised) but this in itself should be fast as it’s just creating new struct instances. SwiftUI is smart enough to diff the resulting structure against the previous one and will only re-evaluate the body of each child view if it think its identity has changed. So in practice, nothing outside the part of the view that has changed should actually have its own body evaluated or need to be re-rendered. |
Beta Was this translation helpful? Give feedback.
-
Description
Hi!
Recently I migrated some of the stuff from deprecated
WithViewStore
to@ObservableState
and I noticed that the overall performance dropped a lot for more complex application that we work on. That is because now the parent component and it's view which contains multiple child sub-components downstream the views hierarchy is being reloaded way too often after the change and the scope of the reloading mechanism doesn't work as expected.I managed to recreate the problem on a vanilla SwiftUI project with TCA used only on a very simple view which I am attaching in zip file.
The problem:
If we have a View as following:
and the
wasEverScrolledByUser
property state changes, then the wholevar body: some View
will be reloaded - not only the text views within the if ... else ... statement but also theText("Some another view that shouldn't be reloaded")
or evenVStack
andZStack
.With usage of
WithViewStore()
it works as expected - so the view within the WithViewStore scope is being reloaded, and now after migration it's a huge performance issue since we end up in a way more reloads than expected.Checklist
main
branch of this package.Expected behavior
The whole
var body: some View
in ParentView - shouldn't be reloaded, but only theif store.wasEverScrolledByUser { Text("This view was never scrolled") }
that is scoped for observation.
This is how it works with the old approach (eg. using
WithViewStore(store: store, observe: { $0.wasEverScrolledByUser })
).Actual behavior
Whole ParentView body is being reloaded when there is a change in the
store.wasEverScrolledByUser
property even though theText("Some another view that shouldn't be reloaded")
or the views that are higher in the views hierarchy likeZStack
orVStack
are not in the scope of the observation.Reproducing project
TheComposableArchitectureObservableState.zip
You can easily recreate the problem as following:
if store.wasEverScrolledByUser
but not applied to the views that are independent of the state change).The Composable Architecture version information
1.19.0
Destination operating system
iOS 18.4
Xcode version information
Xcode 16.2, Xcode 16.3
Swift Compiler version information
Beta Was this translation helpful? Give feedback.
All reactions