@StateObject είναι ένα property wrapper στο SwiftUI που δημιουργεί και κατέχει ένα στιγμιότυπο ObservableObject καθ' όλη τη διάρκεια του κύκλου ζωής του View. Όταν το View εμφανίζεται για πρώτη φορά στην οθόνη, το @StateObject αρχικοποιεί το αντικείμενο και το αποθηκεύει μέχρι να αφαιρεθεί το View από τη μνήμη. Αυτό εγγυάται ότι τα δεδομένα δεν θα επαναφέρονται κατά την ανακατασκευή της διεπαφής — για παράδειγμα, κατά την αλλαγή θέματος ή την ενημέρωση του γονικού View. Σύμφωνα με το Apple Developer Documentation (2025), το @StateObject θα πρέπει να χρησιμοποιείται ως η κύρια πηγή αλήθειας (source of truth) για το ObservableObject στην ιεραρχία του SwiftUI, ενώ τα θυγατρικά View λαμβάνουν το ήδη δημιουργημένο αντικείμενο μέσω @ObservedObject ή @EnvironmentObject.
Κύρια σημεία
@StateObject είναι ένα property wrapper που εισήχθη στο iOS 14 και επιτρέπει σε ένα View να δημιουργεί και να κατέχει ένα στιγμιότυπο μιας κλάσης που συμμορφώνεται με το πρωτόκολλο ObservableObject. Σε αντίθεση με το @State που λειτουργεί με τύπους τιμών (structs), το @StateObject προορίζεται για τύπους αναφοράς — κλάσεις που μπορούν να ειδοποιούν το SwiftUI για αλλαγές στις ιδιότητές τους.
Όταν ένα View χρησιμοποιεί @StateObject var viewModel: MyViewModel, το SwiftUI δημιουργεί αυτόματα το στιγμιότυπο MyViewModel κατά την πρώτη εμφάνιση του View και το αποθηκεύει σε ειδικό χώρο αποθήκευσης του framework. Σε κάθε ενημέρωση του View (για παράδειγμα, κατά την αλλαγή της γονικής κατάστασης), το SwiftUI δεν αναδημιουργεί το αντικείμενο — χρησιμοποιεί το υπάρχον στιγμιότυπο μέχρι να αφαιρεθεί το View από την ιεραρχία.
Σύμφωνα με το Apple WWDC Session 10137 (2024), το @StateObject λύνει το πρόβλημα απώλειας δεδομένων κατά την ανακατασκευή του View που υπήρχε στο iOS 13, όταν οι προγραμματιστές έπρεπε να δημιουργούν το ObservableObject στο γονικό View και να το μεταβιβάζουν μέσω του αρχικοποιητή. Αυτό οδηγούσε σε διπλασιασμό κώδικα και κίνδυνο τυχαίας αναδημιουργίας του αντικειμένου.
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Αριθμός: \(viewModel.count)")
Button("Αύξηση", action: viewModel.increment)
}
}
}
Ο μηχανισμός του @StateObject βασίζεται στην ενσωμάτωση του SwiftUI με το framework Combine. Όταν το ObservableObject επισημαίνει τις ιδιότητές του με το χαρακτηριστικό @Published, το SwiftUI εγγράφεται αυτόματα στις αλλαγές μέσω του publisher που είναι ενσωματωμένος στο πρωτόκολλο ObservableObject. Όταν αλλάζει μια δημοσιευμένη ιδιότητα, το αντικείμενο στέλνει σήμα μέσω του publisher objectWillChange, το οποίο πυροδοτεί την ανασχεδίαση όλων των View που παρατηρούν αυτό το αντικείμενο.
Το SwiftUI αποθηκεύει το στιγμιότυπο ObservableObject σε έναν ειδικό χώρο αποθήκευσης που συνδέεται με ένα συγκεκριμένο στιγμιότυπο View. Αυτός ο χώρος δημιουργείται μία φορά κατά την πρώτη απόδοση και υπάρχει μέχρι την καταστροφή του View. Γι' αυτό ακριβώς το @StateObject εγγυάται τη σταθερότητα της αναφοράς στο αντικείμενο — το SwiftUI διαχειρίζεται αυτόματα τη μνήμη, χωρίς να βασίζεται στον αρχικοποιητή του View.
Σύμφωνα με το objc.io — Thinking in SwiftUI (2025), η εσωτερική υλοποίηση του @StateObject χρησιμοποιεί έναν μηχανισμό παρόμοιο με το @State, αλλά για τύπους αναφοράς: το SwiftUI δημιουργεί ένα περίβλημα boxing γύρω από το αντικείμενο και διαχειρίζεται τον κύκλο ζωής του μέσω του δικού του εκχωρητή, βελτιστοποιημένου για συχνές ανακατασκευές της ιεραρχίας View.
Η κύρια διαφορά μεταξύ @StateObject και @ObservedObject είναι το ποιος κατέχει το αντικείμενο. Το @StateObject δημιουργεί και αποθηκεύει το αντικείμενο — είναι ο ιδιοκτήτης. Το @ObservedObject απλώς παρατηρεί ένα αντικείμενο που δημιουργήθηκε κάπου αλλού και μεταβιβάστηκε μέσω αρχικοποιητή ή ιδιότητας.
| Χαρακτηριστικό | @StateObject | @ObservedObject |
|---|---|---|
| Ιδιοκτησία | Δημιουργεί και κατέχει το αντικείμενο | Μόνο παρατηρεί |
| Αρχικοποίηση | Εντός του View μέσω init/default | Από έξω, μεταβιβάζεται μέσω παραμέτρου |
| Κύκλος ζωής | Συνδεδεμένος με τον κύκλο ζωής του View | Δεν ελέγχεται από το View |
| Αναδημιουργία | Δεν αναδημιουργείται κατά την ενημέρωση | Μπορεί να αντικατασταθεί από έξω |
| Έκδοση iOS | iOS 14+ | iOS 13+ |
Ο κανόνας είναι απλός: εάν το View δημιουργεί το ObservableObject — χρησιμοποιήστε @StateObject. Εάν το View απλώς λαμβάνει ένα ήδη έτοιμο αντικείμενο από τον γονέα — χρησιμοποιήστε @ObservedObject. Η παραβίαση αυτού του κανόνα οδηγεί είτε σε απώλεια δεδομένων (εάν χρησιμοποιείτε @ObservedObject για ιδιοκτησία), είτε σε υπερβολική δημιουργία αντικειμένων (εάν χρησιμοποιείτε @StateObject για παρατήρηση).
Το @StateObject πρέπει να χρησιμοποιείται σε εκείνα τα View που αποτελούν την πηγή αλήθειας για ένα συγκεκριμένο σύνολο δεδομένων. Τυπικά σενάρια περιλαμβάνουν οθόνες με δικό τους view model, ριζικές οθόνες στοίβων πλοήγησης και modal παρουσιάσεις που διαχειρίζονται τη δική τους κατάσταση.
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Αποθήκευση") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
Η αρχικοποίηση του @StateObject με παραμέτρους απαιτεί ειδική σύνταξη, καθώς το SwiftUI διαχειρίζεται ανεξάρτητα τη δημιουργία του αντικειμένου. Δεν μπορείτε απλά να μεταβιβάσετε παραμέτρους στον αρχικοποιητή — πρέπει να χρησιμοποιήσετε escaping closure ή ξεχωριστή μέθοδο δημιουργίας.
Σύμφωνα με το Swift by Sundell (2024), η πιο καθαρή μέθοδος είναι η χρήση μιας μεθόδου εργοστασίου ή closure που το SwiftUI θα καλέσει κατά την πρώτη δημιουργία του αντικειμένου. Μια εναλλακτική προσέγγιση — αρχικοποιήστε το ObservableObject στο γονικό View και μεταβιβάστε το μέσω @StateObject χρησιμοποιώντας τον τυπικό αρχικοποιητή.
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
Είναι σημαντικό να θυμάστε ότι ο αρχικοποιητής View με @StateObject πρέπει να χρησιμοποιεί υπογράμμιση πριν από το όνομα της ιδιότητας (_viewModel) για πρόσβαση στο ίδιο το property wrapper, όχι στην τιμή του. Αυτό είναι ένα τυπικό μοτίβο Swift για εργασία με property wrappers σε αρχικοποιητές.
Το πιο συνηθισμένο λάθος είναι η χρήση @ObservedObject αντί για @StateObject για ένα View που θα πρέπει να κατέχει το αντικείμενο. Σε αυτήν την περίπτωση, σε κάθε ανακατασκευή του γονέα, το αντικείμενο θα δημιουργείται εκ νέου, οδηγώντας σε απώλεια όλων των συσσωρευμένων δεδομένων. Αυτό το λάθος είναι ιδιαίτερα ύπουλο σε σύνθετες ιεραρχίες με NavigationStack ή TabView.
Για να αποφύγετε αυτά τα προβλήματα, ακολουθήστε τον απλό κανόνα: ένα @StateObject ανά πηγή αλήθειας. Εάν τα δεδομένα πρέπει να είναι κοινά σε πολλές οθόνες — δημιουργήστε το @StateObject μία φορά στο ριζικό View και μεταβιβάστε το μέσω @ObservedObject ή @EnvironmentObject στα θυγατρικά στοιχεία.
// ❌ Λάθος: @ObservedObject για ιδιοκτησία αντικειμένου
struct BadView: View {
@ObservedObject var vm = ViewModel() // θα αναδημιουργείται σε κάθε ενημέρωση!
}
// ✅ Σωστό: @StateObject για ιδιοκτησία
struct GoodView: View {
@StateObject var vm = ViewModel() // δημιουργήθηκε μία φορά για τη διάρκεια ζωής του View
}
Συχνές Ερωτήσεις
Το @State λειτουργεί με τύπους τιμών (structs, συμβολοσειρές, αριθμοί) και αποθηκεύει την τιμή απευθείας στον χώρο αποθήκευσης του SwiftUI. Το @StateObject λειτουργεί με τύπους αναφοράς — κλάσεις που συμμορφώνονται με το ObservableObject. Το @State είναι κατάλληλο για απλές τοπικές καταστάσεις, το @StateObject για σύνθετα αντικείμενα με λογική και δημοσιευμένες ιδιότητες.
Όχι, το @StateObject είναι διαθέσιμο μόνο από το iOS 14 και άνω. Για iOS 13 χρησιμοποιήστε @ObservedObject και δημιουργήστε το ObservableObject στο γονικό View μέσω @State με χειροκίνητη διαχείριση κύκλου ζωής. Εναλλακτικά — χρησιμοποιήστε @State με struct αντί για class για δεδομένα που δεν απαιτούν σημασιολογία αναφοράς.
Το θυγατρικό View θα δημιουργήσει το δικό του αντίγραφο του ObservableObject, εντελώς ανεξάρτητο από τον γονέα. Οι αλλαγές στο ένα δεν θα αντικατοπτρίζονται στο άλλο. Αυτό είναι σχεδόν πάντα λάθος: χρησιμοποιήστε @ObservedObject για λήψη του αντικειμένου από τον γονέα και @StateObject μόνο για δημιουργία νέου αντικειμένου εντός του View.
Το αντικείμενο καταστρέφεται όταν το View που το δημιούργησε αφαιρεθεί πλήρως από την ιεραρχία SwiftUI. Για μια οθόνη στο NavigationStack, αυτό συμβαίνει κατά το pop από τη στοίβα πλοήγησης. Για ένα modal παράθυρο — κατά το κλείσιμό του. Για TabView — κατά την αλλαγή καρτέλας, εάν το View δεν αποθηκεύεται σε κρυφή μνήμη.
Χρησιμοποιήστε προσαρμοσμένο init με πρόσβαση στο property wrapper μέσω υπογράμμισης: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Αυτό το μοτίβο επιτρέπει τη μεταβίβαση οποιωνδήποτε παραμέτρων στο ObservableObject, διατηρώντας παράλληλα την εγγύηση μοναδικής δημιουργίας του αντικειμένου κατά τη διάρκεια ζωής του View.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης