-
-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug]: HookScope invalidates view on each Environment change #29
Comments
True, this was not well thought out when I implemented it, I'm sorry. |
Only for Context, possibly the |
It's a tough one to be honest! I will try to explore possible solutions. One thing that might work is to utilise class ObservableEnvironment<E>: ObservableObject {
@Environment e: E
init(_ keyPath: KeyPath<EnvironmentValues, E>) {
...
}
} class ObservableEnvironments: ObservableObject {
var environments: [ObservableEnvironment<??>]
var objectWillChangePublisher: PassthroughSubject<Void> // Or any custom publisher that merges all environments' publishers
}
...
class HookDispatch: ObservableObject {
@ObservableObject environments: ObservableEnvironments // Will trigger HookScope re-render
} if we were able to pass I just got back from vacation and haven't even opened Xcode yet, so it's possible it wouldn't work the way I think. One problem that's clear right from the snippet above is that the code will be messy, considering it would require dynamic typing and type erasure to make it all work. On the bright side, if this trick worked, we could use the same approach to make I don't have the exact design in mind yet, but I hope you can get a grasp of what it would look like.. |
Long time no see 🖖 Point-Free just recently introduced new dependency management system to their architecture and it seems like it could improve our Context API performance by a lot. I explored our options a little, and came up with API build on top of Point-Free let StringContext = Context<String>.self
let IntContext = Context<Int>.self
func MyView() -> some View {
StringContext.Provider("Provided using context") { // Runs DependencyValues._current.withValues
IntContext.Provider(1) { // Runs DependencyValues._current.withValues
let string = useContext(StringContext) // Reads from DependencyValues._current
let int = useContext(IntContext)
Text("\(string) \(int)")
}
}
} As folks from Point-Free described, setting dependencies (context values in our case) one-by-one comes with performance overhead. We could introduce an API to allow users to set all dependencies without nesting: Provider { // Result builder scope
StringContext.provides("Provided using context")
IntContext.provides(1)
} content: { // Runs DependencyValues._current.withValues only once
let string = useContext(StringContext) // Reads from DependencyValues._current
let int = useContext(IntContext)
Text("\(string) \(int)")
} I could open PR if you are interested. This solution unfortunately doesn't solve the underlying issue with |
Checklist
What happened?
Consider a simple SwiftUI app:
where
__App__
is a placeholder for either plain SwiftUI App or its Hooks alternative.SwiftUI App
Hooks App
When you run both apps from Xcode 13.4.1 on iOS 15.5, you will see the following output in the console:
SwiftUIApp()
HooksApp()
SwiftUI - I changed
HookScope - I changed
HookScope - I changed
HookScope - I changed
Considering both apps are stateless and do not invalidate its body, the console result should match.
When tracking down the issue using SwiftUI's
_printChange()
, it's clear that it's caused by library'sHookScopeBody
struct:private struct HookScopeBody<Content: View>: View { @Environment(\.self) private var environment ... var body: some View { if #available(iOS 15.0, *) { + let _ = Self._printChanges() dispatcher.scoped(environment: environment, content) } }
Changes printed by SwiftUI are:
The problem is in observing
Environment(\.self)
which invalidatesHookScopeBody
's body for each change in environment, causing a view to re-render.I haven't dig into finding a solution yet, but wanted to keep the issue on sight as it can potentially cause unexpected re-renders of the whole app.
Notes:
HookView
is also affected (as it internally usesHookScope
)Context
internally uses environments too.Expected Behavior
HookScope
body should only invalidate for key path specified inuseEnvironment
or types used inuseContext
.Reproduction Steps
_App
struct from above__App__
withSwiftUIApp()
and runSwiftUIApp()
call withHooksApp()
Swift Version
5.6+
Library Version
<= 0.0.8
Platform
No response
Scrrenshot/Video/Gif
No response
The text was updated successfully, but these errors were encountered: