SwiftUI @Observable vs @State: Wann welches verwenden in 2026
Beherrsche die Unterschiede zwischen @Observable und @State in SwiftUI, um das richtige Tool für State-Management in iOS-Apps zu wählen.

State-Management bildet das Fundament jeder performanten SwiftUI-Anwendung. Seit iOS 17 hat das @Observable-Macro die Erstellung reaktiver Modelle revolutioniert, während @State weiterhin essenziell für lokalen View-State bleibt. Zu verstehen, wann welches Tool eingesetzt wird, vermeidet unnötige Re-Renders und ermöglicht den Bau flüssiger, reaktionsschneller Apps.
Dieser Artikel beleuchtet die internen Mechanismen von @Observable und @State, ihre grundlegenden Unterschiede und liefert klare Richtlinien zur Wahl des richtigen Tools je nach Kontext. Inklusive der iOS 27 @State-Macro-Änderungen (rückportiert auf iOS 17).
Grundlagen von @State
@State stellt die einfachste Form des State-Managements in SwiftUI dar. Dieser Property Wrapper erzeugt persistenten Speicher für einen Wert, der ausschließlich der View gehört, die ihn deklariert.
struct CounterView: View {
// @State creates storage managed by SwiftUI
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
// The view updates when count changes
Text("Counter: (count)")
.font(.largeTitle)
HStack(spacing: 16) {
Button("- 1") {
count -= 1
}
Button("+ 1") {
count += 1
}
}
.buttonStyle(.borderedProminent)
}
}
}Jede Änderung an count löst einen Re-Render der View aus. SwiftUI verwaltet den Lebenszyklus dieses Werts automatisch und erhält ihn über Body-Rekonstruktionen hinweg.
Schlüsseleigenschaften von @State
@State besitzt mehrere distinktive Eigenschaften, die seinen optimalen Einsatz definieren:
struct FormView: View {
// Simple local state with value types
@State private var username = ""
@State private var isEnabled = true
@State private var selectedIndex = 0
// Complex value types supported
@State private var configuration = FormConfiguration()
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Enabled", isOn: $isEnabled)
Picker("Option", selection: $selectedIndex) {
Text("Option A").tag(0)
Text("Option B").tag(1)
Text("Option C").tag(2)
}
}
}
}
// Structs work perfectly with @State
struct FormConfiguration: Equatable {
var theme: Theme = .light
var fontSize: CGFloat = 16
var showNotifications: Bool = true
}
enum Theme {
case light, dark, system
}Der entscheidende Punkt: @State arbeitet mit Value-Types (Structs, Enums, primitive Typen). Für Reference-Types (Klassen) sind andere Tools erforderlich.
Das @Observable-Macro erklärt
Mit iOS 17 eingeführt, verwandelt @Observable jede Klasse in eine reaktive Datenquelle. Im Gegensatz zum älteren ObservableObject-Protokoll bietet dieses Macro feingranulare Beobachtung: Nur Properties, die von einer View tatsächlich gelesen werden, lösen deren Re-Render aus. Die Dokumentation des Observation-Frameworks liefert die vollständige API-Referenz.
import Observation
// @Observable transforms the class into a reactive source
@Observable
class UserModel {
var name: String = ""
var email: String = ""
var avatarURL: URL?
var preferences = UserPreferences()
// Computed properties work too
var isValid: Bool {
!name.isEmpty && email.contains("@")
}
}
struct UserPreferences {
var newsletter: Bool = false
var notifications: Bool = true
var theme: Theme = .system
}Die Magie geschieht zur Compile-Zeit: Das Macro generiert automatisch den notwendigen Tracking-Code für jede Property.
Granulare Beobachtung in Aktion
Der wesentliche Unterschied zum alten ObservableObject liegt in der Tracking-Granularität:
@Observable
class ProfileModel {
var name: String = ""
var bio: String = ""
var followerCount: Int = 0
var posts: [Post] = []
}
struct ProfileHeaderView: View {
let model: ProfileModel
var body: some View {
VStack {
// This view only re-renders if name or bio change
Text(model.name)
.font(.title)
Text(model.bio)
.foregroundStyle(.secondary)
}
}
}
struct FollowerCountView: View {
let model: ProfileModel
var body: some View {
// This view only re-renders if followerCount changes
HStack {
Image(systemName: "person.2")
Text("(model.followerCount) followers")
}
}
}
struct ProfileScreen: View {
@State private var model = ProfileModel()
var body: some View {
VStack {
// Each subview tracks only its dependencies
ProfileHeaderView(model: model)
FollowerCountView(model: model)
Button("Simulate new follower") {
// Only re-renders FollowerCountView
model.followerCount += 1
}
}
}
}SwiftUI analysiert den body jeder View, um zu bestimmen, welche Properties gelesen werden. Nur diese Properties lösen bei Änderungen einen Re-Render aus.
Direkter Vergleich: @Observable vs @State
Die Wahl zwischen diesen Tools hängt von mehreren Faktoren ab. Hier ein strukturierter Vergleich:
// Scenario 1: Temporary UI state -> @State
struct ToggleExample: View {
@State private var isExpanded = false // @State appropriate
var body: some View {
VStack {
Button(isExpanded ? "Collapse" : "Expand") {
withAnimation {
isExpanded.toggle()
}
}
if isExpanded {
Text("Detailed content...")
}
}
}
}
// Scenario 2: Shared business data -> @Observable
@Observable
class CartModel { // @Observable appropriate
var items: [CartItem] = []
var promoCode: String?
var total: Decimal {
items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
}
var itemCount: Int {
items.reduce(0) { $0 + $1.quantity }
}
func addItem(_ item: CartItem) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index].quantity += 1
} else {
items.append(item)
}
}
func removeItem(_ item: CartItem) {
items.removeAll { $0.id == item.id }
}
}
struct CartItem: Identifiable, Equatable {
let id: UUID
let name: String
let price: Decimal
var quantity: Int
}Übersicht der Use Cases
| Kriterium | @State | @Observable |
|---|---|---|
| Datentyp | Value-Types (struct, enum) | Klassen |
| Geltungsbereich | Lokal in einer View | Über Views teilbar |
| Komplexität | Einfacher State | Komplexe Business-Logik |
| Lebenszyklus | Von SwiftUI verwaltet | Explizit verwaltet |
| Re-Render | Gesamte View | Granular pro Property |
Bereit für deine iOS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
iOS 27 Update: @State als Macro mit Lazy-Initialisierung
WWDC 2026 führte eine signifikante Änderung an @State ein: Es wird jetzt als Macro statt als Property Wrapper implementiert. Diese Änderung adressiert direkt ein häufiges Problem bei der Verwendung von @State mit @Observable-Klassen.
Das Problem vor iOS 27
Zuvor wurden @Observable-Klassen, die in @State gespeichert waren, eager initialisiert. Dies konnte dazu führen, dass dieselbe Instanz mehrfach erstellt wurde, bevor die View überhaupt erschien:
// Before iOS 27: potential multiple initializations
struct StickerStoreView: View {
// The StickerStore instance could be created eagerly
// and recreated during parent view updates
@State private var store = StickerStore()
var body: some View {
// ...
}
}
@Observable
class StickerStore {
init() {
print("StickerStore initialized")
// This could print multiple times before the view appeared
}
}Die Lösung: Lazy-Initialisierung
Mit iOS 27 (und rückportiert auf iOS 17) werden @Observable-Klassen, die in @State gespeichert sind, jetzt lazy initialisiert, genau einmal für die Lebensdauer der View:
// iOS 27+: guaranteed single initialization
struct StickerStoreView: View {
@State private var store = StickerStore()
var body: some View {
// store is created exactly once, when the view first needs it
StoreContentView(store: store)
}
}
@Observable
class StickerStore {
var stickers: [Sticker] = []
init() {
print("StickerStore initialized")
// Now prints exactly once per view lifetime
}
}Bei der Zuweisung von @State in einem Initializer muss der Standardwert entfernt werden, um Compiler-Fehler zu vermeiden:
// Correct pattern when using custom initializer
struct StickerPageView: View {
@State private var page: StickerPage // No default value
let title: String
init(title: String) {
self.page = StickerPage(title: title)
self.title = title
}
var body: some View {
// ...
}
}Diese Korrektur eliminiert verschwenderische Objekt-Allokationen und stellt sicher, dass @Observable-Instanzen korrekt über den View-Lebenszyklus bestehen bleiben. Für Entwickler, die sich auf iOS-Interviewfragen vorbereiten, demonstriert das Verständnis dieser Änderung Wissen über aktuelle SwiftUI-Interna.
Fortgeschrittene Nutzungsmuster
@State und @Observable kombinieren
In realen Anwendungen koexistieren diese Tools harmonisch. @State verwaltet lokalen UI-State, während @Observable Geschäftsdaten kapselt.
@Observable
class TodoListModel {
var todos: [Todo] = []
var filter: TodoFilter = .all
var filteredTodos: [Todo] {
switch filter {
case .all:
return todos
case .active:
return todos.filter { !$0.isCompleted }
case .completed:
return todos.filter { $0.isCompleted }
}
}
func addTodo(title: String) {
let todo = Todo(id: UUID(), title: title, isCompleted: false)
todos.append(todo)
}
func toggleTodo(_ todo: Todo) {
guard let index = todos.firstIndex(where: { $0.id == todo.id }) else { return }
todos[index].isCompleted.toggle()
}
}
struct Todo: Identifiable, Equatable {
let id: UUID
var title: String
var isCompleted: Bool
}
enum TodoFilter: CaseIterable {
case all, active, completed
}
struct TodoListView: View {
// Business data via @Observable (lazy init since iOS 27)
@State private var model = TodoListModel()
// Local UI state via @State
@State private var newTodoTitle = ""
@State private var isAddingTodo = false
@State private var selectedTodo: Todo?
var body: some View {
NavigationStack {
VStack {
// Filter with Picker
Picker("Filter", selection: $model.filter) {
ForEach(TodoFilter.allCases, id: .self) { filter in
Text(filter.label).tag(filter)
}
}
.pickerStyle(.segmented)
.padding()
// Todo list
List(model.filteredTodos, selection: $selectedTodo) { todo in
TodoRowView(todo: todo) {
model.toggleTodo(todo)
}
}
}
.navigationTitle("Tasks")
.toolbar {
Button {
isAddingTodo = true
} label: {
Image(systemName: "plus")
}
}
.sheet(isPresented: $isAddingTodo) {
AddTodoSheet(model: model)
}
}
}
}
struct TodoRowView: View {
let todo: Todo
let onToggle: () -> Void
var body: some View {
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(todo.isCompleted ? .green : .secondary)
.onTapGesture(perform: onToggle)
Text(todo.title)
.strikethrough(todo.isCompleted)
}
}
}
extension TodoFilter {
var label: String {
switch self {
case .all: return "All"
case .active: return "Active"
case .completed: return "Completed"
}
}
}@Observable mit Dependency Injection
Für komplexere Apps ermöglicht die Injektion über das SwiftUI-Environment effektive Entkopplung:
@Observable
class AuthenticationService {
var currentUser: User?
var isAuthenticated: Bool { currentUser != nil }
func login(email: String, password: String) async throws {
// Authentication logic
currentUser = User(id: UUID(), email: email, name: "User")
}
func logout() {
currentUser = nil
}
}
struct User: Identifiable, Equatable {
let id: UUID
let email: String
let name: String
}
// Extension to create an environment key
extension EnvironmentValues {
@Entry var authService: AuthenticationService = AuthenticationService()
}
// Configuration in the App
@main
struct MyApp: App {
@State private var authService = AuthenticationService()
var body: some Scene {
WindowGroup {
ContentView()
.environment(.authService, authService)
}
}
}
// Usage in views
struct ProfileView: View {
@Environment(.authService) private var authService
var body: some View {
if let user = authService.currentUser {
VStack {
Text("Hello, (user.name)")
Button("Sign Out") {
authService.logout()
}
}
} else {
Text("Not signed in")
}
}
}Performance und Optimierung
Unnötige Re-Renders vermeiden
Selbst mit der Granularität von @Observable können bestimmte Muster die Performance beeinträchtigen:
// Bad pattern: reading the entire object
struct BadPatternView: View {
let model: ProfileModel
var body: some View {
// Reads model.name AND model.posts even if only name is displayed
let _ = model.posts.count // Creates unnecessary dependency
Text(model.name)
}
}
// Good pattern: targeted reading
struct GoodPatternView: View {
let model: ProfileModel
var body: some View {
// Tracks only name
Text(model.name)
}
}
// Extract into subviews to isolate dependencies
struct OptimizedProfileView: View {
let model: ProfileModel
var body: some View {
VStack {
// Each subview has its own dependencies
ProfileNameView(model: model)
ProfilePostsView(model: model)
ProfileStatsView(model: model)
}
}
}
struct ProfileNameView: View {
let model: ProfileModel
var body: some View {
Text(model.name)
.font(.title)
}
}
struct ProfilePostsView: View {
let model: ProfileModel
var body: some View {
ForEach(model.posts) { post in
PostRow(post: post)
}
}
}
struct ProfileStatsView: View {
let model: ProfileModel
var body: some View {
HStack {
StatBadge(value: model.followerCount, label: "Followers")
StatBadge(value: model.posts.count, label: "Posts")
}
}
}Computed Properties auf @Observable werden bei jedem Zugriff neu berechnet. Bei aufwendigen Berechnungen empfiehlt sich das Caching des Ergebnisses in einer Stored Property.
Fortgeschrittene Observation mit Swift 6.4
Swift 6.4 führte SE-0506 ein und fügte dem Observation-Framework feingranulare und kontinuierliche Änderungsbenachrichtigungs-APIs hinzu. Diese fortgeschrittenen Tools zielen auf Middleware-Entwickler und Widget-System-Implementierungen ab, nicht auf typische App-Entwicklung. Die meisten Anwendungen verwenden weiterhin das Standard-@Observable-Macro-Pattern. Die Swift 6.4 Release Notes beschreiben diese Ergänzungen im Detail.
Migration von ObservableObject
Für bestehende Projekte mit ObservableObject vereinfacht die Migration zu @Observable den Code:
// Old pattern with ObservableObject
class OldSettingsModel: ObservableObject {
@Published var darkMode: Bool = false
@Published var fontSize: CGFloat = 16
@Published var notifications: Bool = true
}
struct OldSettingsView: View {
@StateObject private var settings = OldSettingsModel()
// or @ObservedObject if injected
var body: some View {
Form {
Toggle("Dark Mode", isOn: $settings.darkMode)
Slider(value: $settings.fontSize, in: 12...24)
Toggle("Notifications", isOn: $settings.notifications)
}
}
}
// New pattern with @Observable
@Observable
class NewSettingsModel {
var darkMode: Bool = false
var fontSize: CGFloat = 16
var notifications: Bool = true
}
struct NewSettingsView: View {
@State private var settings = NewSettingsModel()
var body: some View {
Form {
Toggle("Dark Mode", isOn: $settings.darkMode)
Slider(value: $settings.fontSize, in: 12...24)
Toggle("Notifications", isOn: $settings.notifications)
}
}
}Vorteile der Migration:
- Kein
@Publishedmehr auf jeder Property nötig @Stateersetzt@StateObjectfür die Erstellung (mit garantierter Lazy-Initialisierung)- Automatische granulare Beobachtung
- Lesbarerer und wartbarerer Code
Für Apps, die iOS 17+ anvisieren, wird die Migration zu @Observable empfohlen. Der Combine vs async/await Migrations-Guide behandelt verwandte Modernisierungsmuster.
Praktische Entscheidungsregeln
Hier ein Entscheidungsleitfaden zur Wahl des richtigen Tools:
/*
RULE 1: Ephemeral UI state -> @State
- Animations, transitions
- Local form states
- Temporary selections
- Section expand/collapse
*/
struct AnimatedCard: View {
@State private var isFlipped = false // Local UI state
// ...
}
/*
RULE 2: Shared data across views -> @Observable
- Business data models
- Authentication state
- Shopping cart
- User preferences
*/
@Observable
class UserSession { // Shared across app
var user: User?
var preferences: Preferences
// ...
}
/*
RULE 3: Simple struct with binding -> @State
- Local configuration
- Isolated forms
*/
struct FormData {
var name: String = ""
var email: String = ""
}
struct FormView: View {
@State private var formData = FormData() // Struct with @State
// ...
}
/*
RULE 4: Complex business logic -> @Observable
- Validations
- Network calls
- Data transformations
*/
@Observable
class OrderProcessor { // Complex logic
var items: [OrderItem] = []
var status: OrderStatus = .draft
func validate() -> [ValidationError] { /* ... */ }
func submit() async throws { /* ... */ }
}Sources
- Observation framework documentation - Offizielle Apple API-Referenz
- Swift 6.4 release notes - SE-0506 fortgeschrittenes Observation-Tracking
- WWDC 2026 SwiftUI guide - @State Macro-Änderungen und Lazy-Initialisierung
Die Wahl zwischen @Observable und @State in SwiftUI
Die Wahl zwischen @Observable und @State reduziert sich auf zwei zentrale Fragen: den Datentyp (Wert oder Referenz) und den State-Geltungsbereich (lokal oder geteilt). @State glänzt bei einfachem, lokalem UI-State, während @Observable bei komplexen Datenmodellen mit granularer Beobachtung überzeugt.
Entscheidungs-Checkliste
@Statefür Value-Types und kurzlebigen UI-State verwenden@Observablefür Klassen mit Geschäftsdaten verwenden@Observablebevorzugen, wenn der State mehrere Views umspannt- In Subviews extrahieren, um Re-Renders zu optimieren
- Unnötige Property-Lesezugriffe im Body vermeiden
- Schrittweise von
ObservableObjectmigrieren - Environment für Dependency Injection nutzen
- Performance bei komplexen Fällen mit Instruments testen
Mit den iOS 27 @State-Macro-Verbesserungen (rückportiert auf iOS 17) ist die Kombination dieser Tools zuverlässiger denn je. Die Lazy-Initialisierungs-Korrektur entfernt eine häufige Fehlerquelle bei der Speicherung von @Observable-Klassen in @State.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Findest du den Bug in iOS?
Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Geschrieben von
Anthony Fillion-MailletGründer von SharpSkill
Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.
Aktualisiert am 5. Oktober 2026
Tags
Teilen
Verwandte Artikel

iOS-Entwickler Jobs 2026: Jobbörsen, Gehaltsübersicht und Interviewvorbereitung
Der Arbeitsmarkt für iOS-Entwickler 2026: Wo Stellenangebote zu finden sind, aktuelle Gehaltsspannen und wie sich Bewerber optimal auf technische Interviews vorbereiten.

SwiftUI-Performance: LazyVStack und komplexe Listen optimieren
Optimierungstechniken für LazyVStack und SwiftUI-Listen. Speicherverbrauch reduzieren, Scroll-Performance verbessern und häufige Fallstricke vermeiden.

Eigene SwiftUI-ViewModifier: wiederverwendbare Patterns für Design Systems
Eigene ViewModifier in SwiftUI für ein konsistentes Design System bauen. Patterns, Best Practices und praxisnahe Beispiele für effizientes iOS-View-Styling.