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.

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 @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.
@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.
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:
struct FormView: View {
// ✅ Simple local state - 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.
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:
@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, 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:
// 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 | | Kapsam | Tek görünüme yerel | Görünümler arasında paylaşılabilir | | Karmaşıklık | Basit durum | Karmaşık iş mantığı | | Yaşam döngüsü | SwiftUI tarafından yönetilir | Açıkça yönetilir | | Yeniden render | Tü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.
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.
@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
@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:
@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:
// ❌ 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")
}
}
}@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.
withObservationTracking ile toplu güncellemeler
Gelişmiş senaryolar için withObservationTracking, binding oluşturmadan değişiklikleri tespit etmeye olanak tanır:
import Observation
@Observable
class DataSyncModel {
var lastSyncDate: Date?
var pendingChanges: Int = 0
var isSyncing: Bool = false
}
class SyncCoordinator {
let model: DataSyncModel
init(model: DataSyncModel) {
self.model = model
startObserving()
}
private func startObserving() {
// Observe changes without UI
withObservationTracking {
// Access that creates dependencies
_ = model.pendingChanges
_ = model.isSyncing
} onChange: {
// Called when an observed property changes
Task { @MainActor in
self.handleModelChange()
}
}
}
private func handleModelChange() {
if model.pendingChanges > 0 && !model.isSyncing {
// Trigger synchronization
Task {
await syncChanges()
}
}
// Re-establish observation
startObserving()
}
private func syncChanges() async {
model.isSyncing = true
// Sync logic...
model.isSyncing = false
model.pendingChanges = 0
model.lastSyncDate = Date()
}
}ObservableObject'ten geçiş
ObservableObject kullanan mevcut projelerde @Observable'a geçiş kodu basitleştirir:
// ❌ 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
@Publishedkoymaya gerek yok - Oluşturma için
@StateObjectyerine@State - Otomatik granüler gözlem
- Daha okunabilir ve sürdürülebilir kod
Pratik karar kuralları
Doğru aracı seçmek için karar rehberi:
/*
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 { /* ... */ }
}Sonuç
@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
@Statekullan - ✅ İş verisi içeren sınıflar için
@Observablekullan - ✅ 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
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

SwiftUI Performans: LazyVStack ve Karmaşık Listeleri Optimize Etme
LazyVStack ve SwiftUI listeleri için optimizasyon teknikleri. Bellek tüketimini azaltın, kaydırma performansını iyileştirin ve sık karşılaşılan tuzakları önleyin.

Özel SwiftUI ViewModifier'lar: Design System için yeniden kullanılabilir desenler
Tutarlı bir design system için SwiftUI'da özel ViewModifier'lar oluşturun. iOS view'lerini verimli şekilde stillendirmek için desenler, en iyi uygulamalar ve pratik örnekler.

SwiftUI: iOS icin Modern Arayuzler Olusturma
SwiftUI ile modern kullanici arayuzleri olusturma rehberi: deklaratif sozdizimi, bilesenler, animasyonlar ve iOS 18 icin en iyi uygulamalar.