SwiftUI @Observable vs @State: ใช้ตัวไหนเมื่อไหร่ในปี 2026

เข้าใจความแตกต่างระหว่าง @Observable และ @State ใน SwiftUI เพื่อเลือกเครื่องมือจัดการ state ที่เหมาะกับแอป iOS

การเปรียบเทียบ @Observable และ @State ใน SwiftUI สำหรับนักพัฒนา iOS

การจัดการ state คือรากฐานของแอป SwiftUI ที่มีประสิทธิภาพ ตั้งแต่ iOS 17 เป็นต้นมา macro @Observable ได้พลิกโฉมการสร้างโมเดลแบบ reactive ในขณะที่ @State ยังคงสำคัญสำหรับ state ภายในของ view การเข้าใจว่าควรใช้เครื่องมือใดเมื่อไรช่วยลดการ re-render ที่ไม่จำเป็นและทำให้สร้างแอปที่ลื่นไหลและตอบสนองได้ดี

สิ่งที่บทความนี้ครอบคลุม

บทความนี้สำรวจกลไกภายในของ @Observable และ @State ความแตกต่างพื้นฐาน และแนวทางที่ชัดเจนในการเลือกเครื่องมือที่เหมาะสมตามบริบท รวมถึงการเปลี่ยนแปลง macro @State ใน iOS 27 (backport ไปยัง iOS 17)

พื้นฐาน @State

@State คือรูปแบบการจัดการ state ที่ง่ายที่สุดใน SwiftUI property wrapper นี้สร้างที่จัดเก็บข้อมูลถาวรสำหรับค่าที่เป็นของ view ที่ประกาศมันโดยเฉพาะ

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 จะกระตุ้นให้ view ถูก render ใหม่ SwiftUI จัดการวงจรชีวิตของค่านี้โดยอัตโนมัติ และเก็บรักษาไว้ระหว่างการสร้าง body ใหม่

คุณสมบัติสำคัญของ @State

@State มีคุณสมบัติเด่นหลายประการที่กำหนดการใช้งานที่เหมาะสม

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
}

จุดสำคัญคือ @State ทำงานกับ value type (struct, enum, ชนิดพื้นฐาน) สำหรับ reference type (class) ต้องใช้เครื่องมืออื่น

Macro @Observable อธิบาย

เปิดตัวพร้อม iOS 17 @Observable แปลงคลาสใดก็ตามให้เป็นแหล่งข้อมูลแบบ reactive ต่างจาก protocol ObservableObject แบบเดิม macro นี้ให้การสังเกตการณ์ที่ละเอียดอ่อน เฉพาะ property ที่ view อ่านจริงเท่านั้นที่จะกระตุ้นการ re-render เอกสาร Observation framework ให้ข้อมูลอ้างอิง API ที่ครบถ้วน

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
}

ความมหัศจรรย์เกิดขึ้นในเวลาคอมไพล์ macro สร้างโค้ดติดตามที่จำเป็นสำหรับแต่ละ property โดยอัตโนมัติ

การสังเกตแบบละเอียดในการใช้งานจริง

ความแตกต่างหลักจาก ObservableObject แบบเก่าอยู่ที่ความละเอียดของการติดตาม

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
            }
        }
    }
}
การติดตามอัตโนมัติ

SwiftUI วิเคราะห์ body ของแต่ละ view เพื่อระบุว่ามี property ใดถูกอ่าน เฉพาะ property เหล่านั้นเท่านั้นที่จะกระตุ้นการ re-render เมื่อมีการเปลี่ยนแปลง

เปรียบเทียบโดยตรง: @Observable vs @State

การเลือกระหว่างสองเครื่องมือนี้ขึ้นอยู่กับหลายปัจจัย ต่อไปนี้คือการเปรียบเทียบที่มีโครงสร้าง

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
}

ตารางสรุปกรณีการใช้งาน

เกณฑ์@State@Observable
ประเภทข้อมูลValue type (struct, enum)Class
ขอบเขตภายใน view เดียวแชร์ระหว่าง view ได้
ความซับซ้อนState แบบง่ายลอจิกธุรกิจที่ซับซ้อน
วงจรชีวิตจัดการโดย SwiftUIจัดการอย่างชัดเจน
Re-renderทั้ง viewละเอียดต่อ property

พร้อมที่จะพิชิตการสัมภาษณ์ iOS แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

อัปเดต iOS 27: @State เป็น macro พร้อมการ initialize แบบ lazy

WWDC 2026 เปิดตัวการเปลี่ยนแปลงสำคัญกับ @State: ตอนนี้ถูก implement เป็น macro แทนที่จะเป็น property wrapper การเปลี่ยนแปลงนี้แก้ไขปัญหาที่พบบ่อยเมื่อใช้ @State กับคลาส @Observable โดยตรง

ปัญหาก่อน iOS 27

ก่อนหน้านี้ คลาส @Observable ที่เก็บใน @State ถูก initialize แบบ eager ซึ่งอาจทำให้ instance เดียวกันถูกสร้างหลายครั้งก่อนที่ view จะแสดง:

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
    }
}

วิธีแก้ไข: การ initialize แบบ lazy

ด้วย iOS 27 (และ backport ไปยัง iOS 17) คลาส @Observable ที่เก็บใน @State ถูก initialize แบบ lazy เพียงครั้งเดียวตลอดอายุของ view:

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
    }
}
การเปลี่ยนรูปแบบ initializer

เมื่อกำหนด @State ใน initializer ให้ลบค่า default เพื่อหลีกเลี่ยง error ตอนคอมไพล์:

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

การแก้ไขนี้ขจัดการจัดสรร object ที่ไม่จำเป็นและรับประกันว่า instance ของ @Observable จะคงอยู่อย่างถูกต้องตลอดวงจรชีวิตของ view สำหรับนักพัฒนาที่เตรียมตัวสำหรับคำถามสัมภาษณ์ iOS การเข้าใจการเปลี่ยนแปลงนี้แสดงถึงความรู้เกี่ยวกับ internal ของ SwiftUI ในปัจจุบัน

รูปแบบการใช้งานขั้นสูง

การรวม @State และ @Observable

ในแอปจริง เครื่องมือทั้งสองอยู่ร่วมกันได้อย่างกลมกลืน @State จัดการ state UI ในขณะที่ @Observable ห่อหุ้มข้อมูลธุรกิจ

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"
        }
    }
}

@Observable กับ dependency injection

สำหรับแอปที่ซับซ้อนขึ้น การฉีดผ่าน environment ของ SwiftUI ช่วยแยก dependency ได้อย่างมีประสิทธิภาพ

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")
        }
    }
}

ประสิทธิภาพและการปรับปรุง

หลีกเลี่ยงการ re-render ที่ไม่จำเป็น

แม้ @Observable จะมีความละเอียด รูปแบบบางอย่างก็ยังลดทอนประสิทธิภาพได้

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")
        }
    }
}
Computed property ที่มีต้นทุนสูง

Computed property บน @Observable จะถูกประเมินใหม่ทุกครั้งที่เข้าถึง สำหรับการคำนวณที่ซับซ้อน ควรแคชผลลัพธ์ไว้ใน stored property

การสังเกตขั้นสูงด้วย Swift 6.4

Swift 6.4 เปิดตัว SE-0506 เพิ่ม API แจ้งเตือนการเปลี่ยนแปลงแบบละเอียดและต่อเนื่องให้กับ Observation framework เครื่องมือขั้นสูงเหล่านี้มุ่งเป้าไปที่นักพัฒนา middleware และการ implement ระบบ widget ไม่ใช่สำหรับการพัฒนาแอปทั่วไป แอปส่วนใหญ่ยังคงใช้รูปแบบ macro @Observable มาตรฐาน บันทึกการเผยแพร่ Swift 6.4 อธิบายรายละเอียดการเพิ่มเติมเหล่านี้

การย้ายจาก ObservableObject

สำหรับโปรเจกต์ที่มีอยู่ซึ่งใช้ ObservableObject การย้ายไปใช้ @Observable ทำให้โค้ดง่ายขึ้น

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)
        }
    }
}

ข้อดีของการย้าย:

  • ไม่ต้องใส่ @Published ในแต่ละ property อีกต่อไป
  • @State แทนที่ @StateObject ในการสร้าง (รับประกันการ init แบบ lazy)
  • การสังเกตแบบละเอียดอัตโนมัติ
  • โค้ดอ่านง่ายและดูแลง่ายขึ้น

สำหรับแอปที่รองรับ iOS 17+ แนะนำให้ย้ายไปใช้ @Observable คู่มือการย้าย Combine vs async/await ครอบคลุมรูปแบบการปรับปรุงที่เกี่ยวข้อง

กฎการตัดสินใจในทางปฏิบัติ

คู่มือการตัดสินใจในการเลือกเครื่องมือที่เหมาะสม

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

แหล่งข้อมูล

การเลือกระหว่าง @Observable และ @State ใน SwiftUI

การเลือกระหว่าง @Observable และ @State มาสู่คำถามพื้นฐานสองข้อ ได้แก่ ประเภทข้อมูล (value หรือ reference) และขอบเขตของ state (ภายในหรือใช้ร่วมกัน) @State โดดเด่นสำหรับ state UI แบบง่ายและเฉพาะในขณะที่ @Observable ส่องประกายเมื่อมีโมเดลข้อมูลซับซ้อนที่ต้องการการสังเกตแบบละเอียด

เช็กลิสต์การตัดสินใจ

  • ใช้ @State สำหรับ value type และ state UI ชั่วคราว
  • ใช้ @Observable สำหรับคลาสที่มีข้อมูลธุรกิจ
  • เลือก @Observable เมื่อ state ครอบคลุมหลาย view
  • แยกเป็น subview เพื่อปรับปรุงการ re-render
  • หลีกเลี่ยงการอ่าน property ที่ไม่จำเป็นใน body
  • ย้ายจาก ObservableObject อย่างค่อยเป็นค่อยไป
  • ใช้ environment สำหรับ dependency injection
  • ทดสอบประสิทธิภาพด้วย Instruments สำหรับเคสซับซ้อน

ด้วยการปรับปรุง macro @State ใน iOS 27 (backport ไปยัง iOS 17) การรวมเครื่องมือทั้งสองเข้าด้วยกันเชื่อถือได้มากกว่าที่เคย การแก้ไขการ initialize แบบ lazy ขจัดแหล่งที่มาของบั๊กทั่วไปเมื่อเก็บคลาส @Observable ใน @State

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน iOS เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 5 ตุลาคม 2569

แท็ก

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

แชร์

บทความที่เกี่ยวข้อง

งาน iOS Developer ปี 2026

งาน iOS Developer ปี 2026: แหล่งหางาน ช่วงเงินเดือน และการเตรียมตัวสัมภาษณ์

คู่มือครบถ้วนสำหรับการหางาน iOS Developer ในปี 2026 ค้นพบแหล่งหางานที่ดีที่สุด ข้อมูลเงินเดือนล่าสุด และกลยุทธ์การเตรียมตัวสัมภาษณ์เทคนิค

การปรับแต่งประสิทธิภาพ SwiftUI LazyVStack และรายการที่ซับซ้อน

ประสิทธิภาพ SwiftUI: การปรับแต่ง LazyVStack และรายการที่ซับซ้อน

เทคนิคการปรับแต่งสำหรับ LazyVStack และรายการ SwiftUI ลดการใช้หน่วยความจำ ปรับปรุงประสิทธิภาพการเลื่อน และหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย

ViewModifier ของ SwiftUI สำหรับสร้าง design system ที่นำกลับมาใช้ใหม่ได้ใน iOS

ViewModifier แบบกำหนดเองใน SwiftUI: รูปแบบที่นำกลับมาใช้ใหม่ได้สำหรับ Design System

สร้าง ViewModifier แบบกำหนดเองใน SwiftUI สำหรับ design system ที่สอดคล้องกัน รูปแบบ แนวทางปฏิบัติที่ดีที่สุด และตัวอย่างที่ใช้งานได้จริงสำหรับการจัดสไตล์ view ของ iOS อย่างมีประสิทธิภาพ