@StateObject ist ein Property Wrapper in SwiftUI zum Erstellen und Besitzen einer ObservableObject-Instanz direkt in einer View. SwiftUI garantiert, dass das Objekt einmal pro Lebenszyklus der Ansicht initialisiert und bei erneuten Renderings nicht neu erstellt wird. Laut der Apple Developer Documentation (2025) wird @StateObject für Root-Views empfohlen, die eine Datenquelle erstellen. @StateObject ist die richtige Wahl zum Besitzen eines ObservableObject in der SwiftUI-Hierarchie.
Wichtige Punkte
@StateObject ist ein Property Wrapper, der in SwiftUI 2.0 (iOS 14) eingeführt wurde und die Fähigkeiten von @ObservedObject und @State kombiniert. Wie @ObservedObject abonniert es Änderungen an ObservableObject. Wie @State garantiert es, dass Daten wiederholte Initialisierungen der View-Struktur überleben. @StateObject erstellt das Objekt einmal, wenn die View zum ersten Mal auf dem Bildschirm erscheint, und speichert es im SwiftUI-Heap.
Vor @StateObject verwendeten Entwickler @ObservedObject für alle ObservableObjects, einschließlich der in Views erstellten. Dies führte zu häufigem Datenverlust, wenn die Eltern-View aktualisiert wurde, wodurch die View-Struktur neu erstellt und die @ObservedObject-Instanz mitgenommen wurde. @StateObject löste dies durch die Hinzufügung einer Stabilitätsgarantie.
Die Hauptregel: @StateObject wird in der View verwendet, die das Objekt im Standard-Initialisierer (let model = ViewModel()) erstellt. Kind-Views, die dieses Objekt erhalten, verwenden @ObservedObject. Diese Trennung garantiert eine einzige Quelle der Wahrheit in der gesamten Hierarchie.
SwiftUI verwaltet den Lebenszyklus von @StateObject über einen Storage-Manager, der @State ähnelt. Wenn die View zum ersten Mal erscheint, weist SwiftUI Speicher für das Objekt zu und speichert es in einem persistenten Bereich. Bei nachfolgenden Renderings (Body-Aufrufen) wird das Objekt nicht neu erstellt—die vorhandene Instanz wird verwendet. Das Objekt lebt, solange die View in der Hierarchie ist.
Wenn die View aus der Hierarchie entfernt wird, zerstört SwiftUI das @StateObject und ruft deinit auf. Wenn die View wieder zur Hierarchie hinzugefügt wird, wird eine neue Instanz erstellt. Dies ist beim Design wichtig zu beachten: Wenn Sie Daten zwischen View-Entfernungen bewahren müssen, verwenden Sie eine Service-Schicht (Singleton oder DI) oder @AppStorage für Persistenz.
class TimerViewModel: ObservableObject {
@Published var seconds: Int = 0
private var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
}
deinit {
timer?.invalidate()
}
}
struct TimerView: View {
@StateObject var viewModel = TimerViewModel()
var body: some View {
Text("\(viewModel.seconds)s")
.onAppear { viewModel.start() }
}
}
Im Beispiel wird TimerViewModel über @StateObject erstellt und lebt, solange TimerView auf dem Bildschirm ist. Der Timer startet in onAppear und stoppt in deinit. Wenn @ObservedObject verwendet würde, würde jedes Rendern von TimerView einen neuen TimerViewModel mit Sekunden = 0 erstellen, und der Timer würde niemals korrekt funktionieren. @StateObject garantiert, dass das viewModel eindeutig und stabil ist.
Die Wahl zwischen @StateObject und @ObservedObject hängt davon ab, wer das Objekt besitzt. Wenn die View das Objekt erstellt—@StateObject. Wenn die View ein fertiges Objekt erhält—@ObservedObject. Diese Regel ist so wichtig, dass Xcode eine Warnung ausgibt, wenn @StateObject in einer Kind-View verwendet wird, die das Objekt über einen Initialisierer erhält.
| Situation | Empfohlener Wrapper |
|---|---|
| View erstellt Modell über ViewModel() | @StateObject |
| View erhält Modell vom Eltern-Element | @ObservedObject |
| Modell wird in einer View verwendet | @StateObject |
| Modell wird über Environment übergeben | @EnvironmentObject |
| Modell wird für Vorschauen benötigt | @ObservedObject + Mock |
In der Praxis wird @StateObject zu Beginn eines Projekts oft in der Root-View und @ObservedObject in allen Kind-Views verwendet. Mit dem Wachstum der Anwendung können einige @StateObject-Instanzen durch @EnvironmentObject ersetzt werden, um die Hierarchie zu vereinfachen. @StateObject bleibt jedoch die beste Wahl für modulare Bildschirme mit eigener Logik.
Das erste Muster—MVVM mit @StateObject. ViewModel als ObservableObject wird in der View über @StateObject erstellt. ViewModel enthält @Published-Eigenschaften und Geschäftslogik. Die View abonniert Änderungen und aktualisiert die Oberfläche. Dieser Ansatz bietet testbare Isolation: ViewModel kann ohne UI getestet werden, indem eine Instanz direkt erstellt wird.
Das zweite Muster—@StateObject mit Abhängigkeiten. Wenn ViewModel Dienste benötigt, verwenden Sie die Initialisierung mit Parametern. Zum Beispiel @StateObject var viewModel = UserViewModel(api: APIClient.shared). Seien Sie jedoch vorsichtig: Parameter werden bei jedem Body-Rendering berechnet, aber das Objekt wird nur einmal erstellt. SwiftUI ignoriert nachfolgende @StateObject-Initialisierungen.
Das dritte Muster—verschachtelte @StateObjects. In SwiftUI können mehrere @StateObjects in einer View vorhanden sein, aber das ist selten gerechtfertigt. Normalerweise verwaltet ein @StateObject den gesamten Datensatz der View. Wenn die Logik zu komplex wird, teilen Sie sie in eine Komposition von @ObservedObject-Diensten innerhalb eines @StateObject auf.
struct AppView: View {
@StateObject var router = NavigationRouter()
@StateObject var auth = AuthViewModel()
var body: some View {
ContentView()
.environmentObject(router)
.environmentObject(auth)
}
}
Im Beispiel erstellt AppView zwei @StateObjects: NavigationRouter zur Verwaltung der Navigation und AuthViewModel für die Authentifizierung. Beide Objekte werden über environmentObject in die Environment injiziert. Jede Kind-View kann über @EnvironmentObject auf sie zugreifen, ohne die Initialisiererkette zu durchlaufen.
@StateObject unterstützt die Initialisierung mit beliebigen Parametern, jedoch mit einem wichtigen Vorbehalt: Der Initialisierer wird nur einmal aufgerufen. Bei nachfolgenden Body-Renderings werden neue Parameterwerte ignoriert. Das bedeutet, wenn Sie @State var id: Int = 5 an @StateObject var vm = ViewModel(id: id) übergeben, erhält ViewModel bei Änderung von id den neuen Wert nicht.
Verwenden Sie zur Lösung dieses Problems onReceive oder onAppear zur Synchronisation. Abonnieren Sie Parameteränderungen innerhalb des ViewModel über Combine oder übergeben Sie Parameter über die .onChange(of:)-Methode auf View-Ebene. Eine Alternative ist die Verwendung von @ObservedObject anstelle von @StateObject, wenn das Objekt dynamisch auf externe Änderungen reagieren soll.
struct DetailView: View {
let itemId: Int
@StateObject var viewModel = DetailViewModel()
var body: some View {
Text(viewModel.title)
.onAppear { viewModel.load(id: itemId) }
}
}
Der richtige Ansatz: DetailView erhält itemId als let-Eigenschaft (über den Initialisierer der Struktur übergeben), und @StateObject erstellt DetailViewModel ohne Parameter. In onAppear wird die Methode load(id:) aufgerufen, um Daten für die übergebene ID zu laden. Dies garantiert, dass das ViewModel durch den @StateObject-Mechanismus erstellt wird, aber die Daten bei jedem Erscheinen der View mit der aktuellen ID geladen werden.
Der Hauptfehler—die Verwendung von @StateObject in Kind-Views, die das Objekt von einem Eltern-Element erhalten. Wenn ParentView @StateObject model erstellt und ChildView @StateObject var model: ModelType (mit einem Standardparameter) deklariert, erstellt ChildView eine eigene unabhängige Instanz. Die Eltern- und Kind-Objekte sind nicht verbunden, und Änderungen in einem wirken sich nicht auf das andere aus.
Der zweite Fehler—Platzieren von @StateObject in List oder ForEach. Jedes Listenelement erstellt sein eigenes @StateObject, was zu mehreren unabhängigen Instanzen führt. Für Listen ist der richtige Ansatz, ein einzelnes ObservableObject über @ObservedObject an alle Elemente zu übergeben oder Identifiable-Strukturen mit @State innerhalb von List zu verwenden.
Das dritte Problem—fehlende Bereinigung in deinit. @StateObject lebt für den gesamten View-Lebenszyklus. Wenn das Objekt Timer, Combine-Abonnements oder Netzwerkanfragen erstellt, muss deinit diese abbrechen. Andernfalls sind Speicherlecks und fortgesetzte Hintergrundarbeit nach dem Schließen des Bildschirms unvermeidlich. Verwenden Sie immer einen Combine Cancellable-Speicher oder invalidieren Sie Timer in deinit.
Häufig gestellte Fragen
@StateObject wurde in SwiftUI 2.0 auf der WWDC 2020 zusammen mit iOS 14, macOS 11, watchOS 7 und tvOS 14 hinzugefügt. Davor war @ObservedObject die einzige Möglichkeit, mit ObservableObject zu arbeiten, was häufig zu Datenverlustfehlern führte.
Nein, @StateObject unterstützt keine Optional-Typen. Das Objekt muss bei der Deklaration initialisiert werden. Wenn Sie ein optionales Objekt benötigen, verwenden Sie @ObservedObject oder @EnvironmentObject mit einem optionalen Typ.
Fügen Sie print(#function) zum Initialisierer und deinit des ObservableObject hinzu. Wenn init bei Renderings nicht aufgerufen wird—funktioniert @StateObject korrekt. Wenn init jedes Mal aufgerufen wird—ersetzen Sie @ObservedObject durch @StateObject.
Ja, @StateObject funktioniert in SwiftUI-Views, die über UIHostingController in UIKit eingebettet sind. Der Objektlebenszyklus ist an die SwiftUI-View gebunden, nicht an den UIViewController. Wenn die SwiftUI-View ersetzt wird, wird das @StateObject zerstört.
Mehrere kleine @StateObjects mit getrennten Verantwortlichkeiten. Dies verbessert Testbarkeit, Wiederverwendbarkeit und Leistung—wenn sich ein Objekt ändert, werden nur die abonnierten Teile der Oberfläche neu gezeichnet, nicht die gesamte View.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch