SwiftUI @Observable vs @State: 2026'da Hangisini Ne Zaman Kullanmalı

iOS uygulamaları için doğru durum yönetimi aracını seçmek üzere SwiftUI'daki @Observable ve @State arasındaki farkları öğrenin.

iOS geliştiricileri için SwiftUI'da @Observable ve @State karşılaştırması

Durum yönetimi, performanslı bir SwiftUI uygulamasının temel taşıdır. iOS 17 ile birlikte @Observable makrosu reaktif modellerin oluşturulmasında devrim yaratmış, @State ise yerel görünüm durumu için vazgeçilmez kalmıştır. Hangi aracın ne zaman kullanılacağını anlamak gereksiz yeniden render'ları engeller ve akıcı, duyarlı uygulamaların inşasını sağlar.

Bu makale neyi kapsıyor

Bu makale @Observable ve @State'in iç mekanizmalarını, temel farklarını ve bağlama göre doğru aracı seçmek için net yönergeleri inceler. iOS 27 @State makro değişikliklerini içerir (iOS 17'ye backport edilmiştir).

@State temelleri

@State, SwiftUI'daki en basit durum yönetimi biçimini temsil eder. Bu property wrapper, yalnızca onu deklare eden görünüme ait olan bir değer için kalıcı depolama oluşturur.

CounterView.swiftswift
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)
        }
    }
}

count üzerindeki her değişiklik görünümün yeniden render edilmesini tetikler. SwiftUI bu değerin yaşam döngüsünü otomatik olarak yönetir ve body yeniden oluşturulurken korur.

@State'in temel özellikleri

@State optimum kullanımını tanımlayan birkaç ayırt edici özelliğe sahiptir:

StateCharacteristics.swiftswift
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
}

Kritik nokta: @State değer türleriyle (struct, enum, ilkel türler) çalışır. Referans türleri (sınıflar) için başka araçlar gereklidir.

@Observable makrosu açıklaması

iOS 17 ile sunulan @Observable, herhangi bir sınıfı reaktif bir veri kaynağına dönüştürür. Eski ObservableObject protokolünün aksine, bu makro ince taneli gözlem sunar: yalnızca bir görünüm tarafından gerçekten okunan özellikler yeniden render'ı tetikler. Observation framework belgeleri tam API referansını sağlar.

UserModel.swiftswift
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
}

Sihir derleme zamanında gerçekleşir: makro, her özellik için gereken izleme kodunu otomatik olarak üretir.

Granüler gözlem iş başında

Eski ObservableObject ile asıl fark izleme granülerliğindedir:

GranularObservation.swiftswift
@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
            }
        }
    }
}
Otomatik izleme

SwiftUI, hangi özelliklerin okunduğunu belirlemek için her görünümün body'sini analiz eder. Yalnızca o özellikler değiştiğinde yeniden render tetiklenir.

Doğrudan karşılaştırma: @Observable vs @State

Bu araçlar arasındaki seçim birkaç faktöre bağlıdır. İşte yapılandırılmış bir karşılaştırma:

ComparisonExample.swiftswift
// 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
}

Kullanım durumu özet tablosu

Kriter@State@Observable
Veri türüDeğer türleri (struct, enum)Sınıflar
KapsamTek görünüme yerelGörünümler arasında paylaşılabilir
KarmaşıklıkBasit durumKarmaşık iş mantığı
Yaşam döngüsüSwiftUI tarafından yönetilirAçıkça yönetilir
Yeniden renderTüm görünümÖzellik bazında granüler

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

iOS 27 güncellemesi: Tembel başlatma ile makro olarak @State

WWDC 2026, @State'te önemli bir değişiklik getirdi: artık property wrapper yerine makro olarak uygulanıyor. Bu değişiklik, @State ile @Observable sınıfları kullanılırken yaygın bir sorunu doğrudan çözüyor.

iOS 27 öncesi sorun

Daha önce, @State'te saklanan @Observable sınıfları hevesli (eagerly) başlatılıyordu. Bu, görünüm henüz ortaya çıkmadan aynı örneğin birden çok kez oluşturulmasına neden olabiliyordu:

BeforeIOS27.swiftswift
// 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
    }
}

Çözüm: tembel başlatma

iOS 27 (ve iOS 17'ye backport) ile @State'te saklanan @Observable sınıfları artık tembel olarak, görünümün yaşam süresi boyunca tam olarak bir kez başlatılıyor:

AfterIOS27.swiftswift
// 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
    }
}
Başlatıcı kalıp değişikliği

Bir başlatıcıda @State atarken, derleyici hatalarından kaçınmak için varsayılan değeri kaldırın:

InitializerPattern.swiftswift
// 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 {
        // ...
    }
}

Bu düzeltme, gereksiz nesne tahsislerini ortadan kaldırır ve @Observable örneklerinin görünüm yaşam döngüsü boyunca doğru şekilde kalıcı olmasını sağlar. iOS mülakat sorularına hazırlanan geliştiriciler için bu değişikliği anlamak, güncel SwiftUI dahili mekanizmalarına hakimiyet gösterir.

Gelişmiş kullanım kalıpları

@State ve @Observable'ı birleştirme

Gerçek uygulamalarda bu araçlar uyum içinde birlikte var olur. @State yerel UI durumunu yönetirken @Observable iş verisini kapsüller.

CombinedPatterns.swiftswift
@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"
        }
    }
}

Bağımlılık enjeksiyonuyla @Observable

Daha karmaşık uygulamalar için SwiftUI environment üzerinden enjeksiyon, etkili bir ayrıştırma sağlar:

DependencyInjection.swiftswift
@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")
        }
    }
}

Performans ve optimizasyon

Gereksiz yeniden render'lardan kaçınma

@Observable'ın granülerliğine rağmen bazı kalıplar performansı düşürebilir:

PerformanceOptimization.swiftswift
// 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")
        }
    }
}
Maliyetli computed property'ler

@Observable üzerindeki computed property'ler her erişimde yeniden hesaplanır. Karmaşık hesaplamalar için sonucu stored property'de cache'lemek mantıklıdır.

Swift 6.4 ile gelişmiş gözlem

Swift 6.4, SE-0506'yı tanıtarak Observation framework'e ince taneli ve sürekli değişiklik bildirimi API'leri ekledi. Bu gelişmiş araçlar, tipik uygulama geliştirme yerine middleware geliştiricileri ve widget sistem uygulamaları için tasarlanmıştır. Çoğu uygulama standart @Observable makro kalıbını kullanmaya devam ediyor. Swift 6.4 sürüm notları bu eklemeleri detaylandırıyor.

ObservableObject'ten geçiş

ObservableObject kullanan mevcut projelerde @Observable'a geçiş kodu basitleştirir:

MigrationExample.swiftswift
// 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)
        }
    }
}

Geçişin avantajları:

  • Her özelliğe @Published koymaya gerek yok
  • Oluşturma için @StateObject yerine @State (garantili tembel başlatma ile)
  • Otomatik granüler gözlem
  • Daha okunabilir ve sürdürülebilir kod

iOS 17+ hedefleyen uygulamalar için @Observable'a geçiş önerilir. Combine vs async/await geçiş rehberi ilgili modernizasyon kalıplarını kapsar.

Pratik karar kuralları

Doğru aracı seçmek için karar rehberi:

DecisionGuide.swiftswift
/*
 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 { /* ... */ }
}

Kaynaklar

SwiftUI'da @Observable ve @State arasında seçim

@Observable ile @State arasındaki seçim iki temel soruya iner: veri türü (değer mi referans mı) ve durum kapsamı (yerel mi paylaşılan mı). @State basit, yerel UI durumlarında parlarken @Observable granüler gözlem gerektiren karmaşık veri modellerinde öne çıkar.

Karar kontrol listesi

  • Değer türleri ve geçici UI durumu için @State kullan
  • İş verisi içeren sınıflar için @Observable kullan
  • Durum birden çok görünümü kapsadığında @Observable'ı tercih et
  • Yeniden render'ları optimize etmek için alt görünümlere ayır
  • Body'de gereksiz özellikleri okumaktan kaçın
  • ObservableObject'ten kademeli olarak geç
  • Bağımlılık enjeksiyonu için environment kullan
  • Karmaşık durumlarda Instruments ile performansı test et

iOS 27 @State makro iyileştirmeleri (iOS 17'ye backport edilmiştir) sayesinde bu araçları birleştirmek her zamankinden daha güvenilirdir. Tembel başlatma düzeltmesi, @Observable sınıflarını @State'te saklarken yaygın bir hata kaynağını ortadan kaldırır.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Günün meydan okuması

iOS kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

5 Ekim 2026 tarihinde güncellendi

Etiketler

#swiftui
#ios
#observable
#state-management
#swift

Paylaş

İlgili makaleler