-
-
Notifications
You must be signed in to change notification settings - Fork 65
StateObject creation with wrappedValue #7
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds guidance on the correct pattern for initializing @StateObject with parameters in a view's custom initializer. It addresses a subtle performance issue where creating an object instance outside the autoclosure parameter can lead to unnecessary object allocations during SwiftUI's view initialization process.
Changes:
- Adds new subsection "@StateObject instantiation in View's initializer" to state-management.md
- Provides wrong vs. correct examples showing how to pass initialization parameters to @StateObject
- Includes attribution to Vincent Pradeilles for highlighting this pattern
AvdLee
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one, I learned something new and alongside, our agents will do now, too! Thanks for your contribution 💪🏻
|
Same for me ) Team work - makes the dream work! |
|
The issue here is that initializing |
Yes, but often that StateObjects needs an external data. |
No, never. If you explain your use case we can show you how to use bindings, combine pipelines or observable to solve your problem. Usually this is done by those that haven't learning bindings. |
If we omit the Architectural question: this is might be a possible use case // MARK: - Domain model (shared, reference type)
final class Movie: ObservableObject, Identifiable {
let id: UUID
@Published var title: String
@Published var isFavorite: Bool
init(id: UUID = UUID(), title: String, isFavorite: Bool) {
self.id = id
self.title = title
self.isFavorite = isFavorite
}
}
final class MovieEditSession: ObservableObject {
@Published var title: String
@Published var isFavorite: Bool
private let movie: Movie
init(movie: Movie) {
self.movie = movie
self.title = movie.title
self.isFavorite = movie.isFavorite
}
func commit() {
movie.title = title
movie.isFavorite = isFavorite
}
func discard() {
title = movie.title
isFavorite = movie.isFavorite
}
}
// MARK: - View
struct MovieDetailsView: View {
@ObservedObject var movie: Movie
@StateObject private var session: MovieEditSession
init(movie: Movie) {
self.movie = movie
_session = StateObject(wrappedValue: MovieEditSession(movie: movie))
}
var body: some View {
Form {
TextField("Title", text: $session.title)
Toggle("Favorite", isOn: $session.isFavorite)
HStack {
Button("Discard") {
session.discard()
}
Spacer()
Button("Save") {
session.commit()
}
}
}
}
} |
|
Thanks for sharing, at first glance I see a few issues but I’ll follow up tomorrow with a full solution. Movie class can’t have id UUID for Identifiable, class vis idenity by its pointer. But yhis one can be a struct since doesn’t have any relations. |
|
This refactor moves the logic away from the "StateObject init trap" and embraces the Value Type nature of SwiftUI. By changing the session to a Something like this: |
|
@malhal Thanks for make a snippet and explanation. FavoriteEditorConfig is acting like a bridge between states. That will remove wrapper. But situations like in PR are exists and not always we can decouple it. Yes, it's an architectural question. What if there will be a more complex logic in MovieEditSession? Add other configs? |
|
Actually there shouldn't be a single situation the PR is required to break the state as a source of truth design. The Beyond just handling Bindings, the key principle here is that SwiftUI requires shared state to live in the highest common ancestor and be passed down (as a let for read-only or a Binding for read-write, via computed vars for more complex data flows). By leveraging this and using a proper configuration pattern, we can satisfy complex logic requirements while completely avoiding the |
@malhal Agree that it's probably smelly-code, but let's just assume that StateObject has an init with wrappedValue and we are avoiding re-instantiation beyond @autoclosure. Out of DI scope. Since we are not telling LLM to use this init. Just check the init structure. |
|
Actually I think there is no problem init the @StateObject in the init of the view as long as you know that it will not update when the movie change, so this means that the view it self is the parent view which mean that movie will not change and instead it is like needing info from the previous screen. |
|
You've highlighted a common trap: Views aren't screens. Even if a View feels like a new screen, the SwiftUI hierarchy doesn't guarantee it won't be re-initialized. By relying on the "as long as you know it won't change" rule, you're fighting the framework's lifecycle. If you lift that |
Suggesting to add this info in new PR. Will mention you there. |
When you need to create StateObject from wrapper which relies on DI values (either in constructor or in method), it should avoid objects creation out of auto-closure.
This was highlighted by Vincent Pradeilles: https://www.linkedin.com/in/vincentpradeilles/overlay/about-this-profile/