@StateObject: คืออะไร ความแตกต่างจาก @ObservedObject และตัวอย่าง

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-19 เวลาอ่าน: 7 นาที

@StateObject คือ Property Wrapper ใน SwiftUI สำหรับสร้างและเป็นเจ้าของอินสแตนซ์ ObservableObject โดยตรงภายใน view SwiftUI รับประกันว่าออบเจ็กต์จะถูกเริ่มต้นหนึ่งครั้งต่อวงจรชีวิตของวิวและจะไม่ถูกสร้างใหม่เมื่อมีการเรนเดอร์ซ้ำ ตาม เอกสารสำหรับนักพัฒนา Apple (2025) @StateObject แนะนำสำหรับวิวรากที่สร้างแหล่งข้อมูล @StateObject เป็นตัวเลือกที่ถูกต้องสำหรับการเป็นเจ้าของ ObservableObject ในลำดับชั้นของ SwiftUI

ประเด็นสำคัญ

  • @StateObject — Property Wrapper สำหรับสร้างและเป็นเจ้าของ ObservableObject ใน view
  • อินสแตนซ์เดียว — ออบเจ็กต์ถูกสร้างครั้งเดียวและไม่ถูกสร้างใหม่เมื่อเรนเดอร์ซ้ำ
  • แหล่งความจริง — @StateObject รับประกันความเสถียรของข้อมูลสำหรับลำดับชั้นทั้งหมด
  • ความแตกต่างจาก @ObservedObject — @ObservedObject ไม่เป็นเจ้าของออบเจ็กต์และอาจสูญเสียมัน
  • วิวราก — @StateObject ถูกใช้ในวิวที่สร้างออบเจ็กต์

@StateObject ใน SwiftUI คืออะไร?

@StateObject คือ Property Wrapper ที่เปิดตัวใน SwiftUI 2.0 (iOS 14) ซึ่งรวมความสามารถของ @ObservedObject และ @State เข้าด้วยกัน เช่นเดียวกับ @ObservedObject มันสมัครรับการเปลี่ยนแปลงของ ObservableObject เช่นเดียวกับ @State มันรับประกันว่าข้อมูลจะอยู่รอดผ่านการเริ่มต้นโครงสร้างวิวซ้ำ ๆ @StateObject สร้างออบเจ็กต์หนึ่งครั้งเมื่อวิวปรากฏบนหน้าจอครั้งแรกและเก็บไว้ในฮีปของ SwiftUI

ก่อน @StateObject นักพัฒนาใช้ @ObservedObject สำหรับ ObservableObjects ทั้งหมด รวมถึงที่สร้างในวิว สิ่งนี้นำไปสู่การสูญเสียข้อมูลบ่อยครั้งเมื่อวิวแม่ถูกอัปเดต ทำให้โครงสร้างวิวถูกสร้างใหม่และพาอินสแตนซ์ @ObservedObject ไปด้วย @StateObject แก้ปัญหานี้โดยเพิ่มการรับประกันความเสถียร

กฎหลัก: @StateObject ถูกใช้ในวิวที่สร้างออบเจ็กต์ในตัวเริ่มต้นเริ่มต้น (let model = ViewModel()) วิวลูกที่รับออบเจ็กต์นี้ใช้ @ObservedObject การแยกนี้รับประกันแหล่งความจริงเดียวทั่วทั้งลำดับชั้น

วงจรชีวิตของ @StateObject

SwiftUI จัดการวงจรชีวิตของ @StateObject ผ่านตัวจัดการพื้นที่เก็บข้อมูลที่คล้ายกับ @State เมื่อวิวปรากฏครั้งแรก SwiftUI จัดสรรหน่วยความจำสำหรับออบเจ็กต์และเก็บไว้ในพื้นที่ถาวร เมื่อเรนเดอร์ซ้ำ (การเรียก body) ออบเจ็กต์จะไม่ถูกสร้างใหม่—ใช้อินสแตนซ์ที่มีอยู่ ออบเจ็กต์มีชีวิตตราบเท่าที่วิวยังอยู่ในลำดับชั้น

เมื่อวิวถูกลบออกจากลำดับชั้น SwiftUI ทำลาย @StateObject และเรียก deinit เมื่อวิวถูกเพิ่มกลับเข้าไปในลำดับชั้น จะสร้างอินสแตนซ์ใหม่ สิ่งสำคัญที่ต้องพิจารณาเมื่อออกแบบ: หากคุณต้องการเก็บข้อมูลระหว่างการลบวิว ให้ใช้เลเยอร์บริการ (singleton หรือ DI) หรือ @AppStorage เพื่อความคงทน

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

ในตัวอย่าง TimerViewModel ถูกสร้างผ่าน @StateObject และมีชีวิตตราบเท่าที่ TimerView อยู่บนหน้าจอ ตัวจับเวลาเริ่มใน onAppear และหยุดใน deinit หากใช้ @ObservedObject ทุกครั้งที่ TimerView เรนเดอร์จะสร้าง TimerViewModel ใหม่ด้วย seconds = 0 และตัวจับเวลาจะไม่ทำงานอย่างถูกต้อง @StateObject รับประกันว่า viewModel มีเอกลักษณ์และเสถียร

@StateObject vs @ObservedObject: การเปรียบเทียบ

การเลือกระหว่าง @StateObject และ @ObservedObject ขึ้นอยู่กับใครเป็นเจ้าของออบเจ็กต์ ถ้าวิวสร้างออบเจ็กต์—@StateObject ถ้าวิวนรับออบเจ็กต์สำเร็จรูป—@ObservedObject กฎนี้สำคัญมากจน Xcode แสดงคำเตือนเมื่อใช้ @StateObject ในวิวลูกที่รับออบเจ็กต์ผ่านตัวเริ่มต้น

สถานการณ์Wrapper ที่แนะนำ
วิวสร้างโมเดลผ่าน ViewModel()@StateObject
วิวรับโมเดลจากแม่@ObservedObject
โมเดลถูกใช้ในวิวเดียว@StateObject
โมเดลถูกส่งผ่าน Environment@EnvironmentObject
โมเดลจำเป็นสำหรับตัวอย่างดูก่อน@ObservedObject + mock

ในทางปฏิบัติ เมื่อเริ่มโปรเจกต์ @StateObject มักใช้ในวิวรากและ @ObservedObject ในวิวลูกทั้งหมด เมื่อแอปพลิเคชันเติบโตขึ้น อินสแตนซ์ @StateObject บางตัวสามารถถูกแทนที่ด้วย @EnvironmentObject เพื่อทำให้ลำดับชั้นง่ายขึ้น อย่างไรก็ตาม @StateObject ยังคงเป็นตัวเลือกที่ดีที่สุดสำหรับหน้าจอแบบโมดูลาร์ที่มีตรรกะของตัวเอง

รูปแบบการใช้งาน @StateObject

รูปแบบแรก—MVVM กับ @StateObject ViewModel ในฐานะ ObservableObject ถูกสร้างในวิวผ่าน @StateObject ViewModel มีคุณสมบัติ @Published และตรรกะทางธุรกิจ วิวสมัครรับการเปลี่ยนแปลงและอัปเดตอินเทอร์เฟซ วิธีการนี้ให้การแยกที่ทดสอบได้: ViewModel สามารถทดสอบโดยไม่ต้องมี UI โดยการสร้างอินสแตนซ์โดยตรง

รูปแบบที่สอง—@StateObject ที่มีการพึ่งพา ถ้า ViewModel ต้องการบริการ ให้ใช้การเริ่มต้นด้วยพารามิเตอร์ ตัวอย่างเช่น @StateObject var viewModel = UserViewModel(api: APIClient.shared) อย่างไรก็ตามโปรดระวัง: พารามิเตอร์ถูกคำนวณทุกครั้งที่ body เรนเดอร์ แต่ออบเจ็กต์ถูกสร้างเพียงครั้งเดียว SwiftUI ไม่สนใจการเริ่มต้น @StateObject ครั้งต่อมา

รูปแบบที่สาม—@StateObject ซ้อนกัน ใน SwiftUI คุณสามารถมี @StateObject หลายตัวในวิวเดียว แต่สิ่งนี้ไม่ค่อยสมเหตุสมผล โดยปกติ @StateObject หนึ่งตัวจัดการชุดข้อมูลทั้งหมดของวิว ถ้าตรรกะซับซ้อนเกินไป ให้แบ่งมันเป็นการประกอบของบริการ @ObservedObject ภายใน @StateObject เดียว

swift
struct AppView: View {
    @StateObject var router = NavigationRouter()
    @StateObject var auth = AuthViewModel()

    var body: some View {
        ContentView()
            .environmentObject(router)
            .environmentObject(auth)
    }
}

ในตัวอย่าง AppView สร้าง @StateObject สองตัว: NavigationRouter สำหรับจัดการการนำทางและ AuthViewModel สำหรับการตรวจสอบสิทธิ์ ออบเจ็กต์ทั้งสองถูกฉีดเข้าไปใน Environment ผ่าน environmentObject วิวลูกใด ๆ สามารถเข้าถึงมันผ่าน @EnvironmentObject โดยไม่ต้องผ่านลูกโซ่ตัวเริ่มต้น

@StateObject และการเริ่มต้นด้วยพารามิเตอร์

@StateObject รองรับการเริ่มต้นด้วยพารามิเตอร์ใด ๆ แต่มีข้อสำคัญ: ตัวเริ่มต้นถูกเรียกเพียงครั้งเดียว เมื่อเรนเดอร์ body ซ้ำ ค่าพารามิเตอร์ใหม่จะถูกไม่สนใจ ซึ่งหมายความว่าถ้าคุณส่ง @State var id: Int = 5 ไปยัง @StateObject var vm = ViewModel(id: id) เมื่อ id เปลี่ยน ViewModel จะไม่ได้รับค่าใหม่

เพื่อแก้ปัญหานี้ ให้ใช้ onReceive หรือ onAppear สำหรับการซิงโครไนซ์ สมัครรับการเปลี่ยนแปลงพารามิเตอร์ภายใน ViewModel ผ่าน Combine หรือส่งพารามิเตอร์ผ่านเมธอด .onChange(of:) ที่ระดับวิว อีกทางเลือกคือใช้ @ObservedObject แทน @StateObject ถ้าออบเจ็กต์ควรตอบสนองต่อการเปลี่ยนแปลงภายนอกแบบไดนามิก

swift
struct DetailView: View {
    let itemId: Int
    @StateObject var viewModel = DetailViewModel()

    var body: some View {
        Text(viewModel.title)
            .onAppear { viewModel.load(id: itemId) }
    }
}

วิธีการที่ถูกต้อง: DetailView รับ itemId เป็นคุณสมบัติ let (ส่งผ่านตัวเริ่มต้นของโครงสร้าง) และ @StateObject สร้าง DetailViewModel โดยไม่มีพารามิเตอร์ ใน onAppear เมธอด load(id:) ถูกเรียกเพื่อโหลดข้อมูลสำหรับ ID ที่ส่ง สิ่งนี้รับประกันว่า ViewModel ถูกสร้างโดยกลไก @StateObject แต่ข้อมูลจะถูกโหลดทุกครั้งที่วิวปรากฏด้วย ID ปัจจุบัน

ข้อผิดพลาดทั่วไปกับ @StateObject

ข้อผิดพลาดหลัก—การใช้ @StateObject ในวิวลูกที่รับออบเจ็กต์จากแม่ ถ้า ParentView สร้าง @StateObject model และ ChildView ประกาศ @StateObject var model: ModelType (พร้อมพารามิเตอร์เริ่มต้น) ChildView จะสร้างอินสแตนซ์อิสระของตัวเอง ออบเจ็กต์แม่และลูกจะไม่เชื่อมต่อกัน และการเปลี่ยนแปลงในอันหนึ่งจะไม่สะท้อนในอีกอัน

ข้อผิดพลาดที่สอง—การวาง @StateObject ใน List หรือ ForEach แต่ละองค์ประกอบของรายการสร้าง @StateObject ของตัวเอง นำไปสู่อินสแตนซ์อิสระหลายตัว สำหรับรายการ วิธีการที่ถูกต้องคือส่ง ObservableObject หนึ่งตัวไปยังองค์ประกอบทั้งหมดผ่าน @ObservedObject หรือใช้โครงสร้าง Identifiable กับ @State ภายใน List

ปัญหาที่สาม—ขาดการล้างข้อมูลใน deinit @StateObject มีชีวิตตลอดวงจรชีวิตของวิวทั้งหมด ถ้าออบเจ็กต์สร้างตัวจับเวลา การสมัครรับ Combine หรือคำขอเครือข่าย deinit ต้องยกเลิก它们 มิฉะนั้น หน่วยความจำรั่วและการทำงานพื้นหลังต่อเนื่องหลังจากปิดหน้าจอเป็นสิ่งที่หลีกเลี่ยงไม่ได้ ใช้พื้นที่เก็บ Cancellable ของ Combine เสมอหรือทำให้ตัวจับเวลาไม่ถูกต้องใน deinit

คำถามที่พบบ่อย

@StateObject ถูกแนะนำใน SwiftUI เมื่อใด?

@StateObject ถูกเพิ่มใน SwiftUI 2.0 ที่ WWDC 2020 พร้อมกับ iOS 14, macOS 11, watchOS 7 และ tvOS 14 ก่อนหน้านั้น @ObservedObject เป็นวิธีเดียวในการทำงานกับ ObservableObject ซึ่งมักนำไปสู่บักการสูญเสียข้อมูล

@StateObject สามารถเป็นทางเลือกได้หรือไม่?

ไม่ @StateObject ไม่รองรับชนิด Optional ออบเจ็กต์ต้องถูกเริ่มต้นที่การประกาศ หากคุณต้องการออบเจ็กต์ที่เป็นทางเลือก ให้ใช้ @ObservedObject หรือ @EnvironmentObject พร้อมชนิดที่เป็นทางเลือก

จะตรวจสอบได้อย่างไรว่า @StateObject ถูกสร้างเพียงครั้งเดียว?

เพิ่ม print(#function) ในตัวเริ่มต้นและ deinit ของ ObservableObject ถ้า init ไม่ถูกเรียกเมื่อเรนเดอร์ซ้ำ—@StateObject ทำงานถูกต้อง ถ้า init ถูกเรียกทุกครั้ง—แทนที่ @ObservedObject ด้วย @StateObject

@StateObject สามารถใช้กับ UIKit ผ่าน UIHostingController ได้หรือไม่?

ใช่ @StateObject ทำงานในวิว SwiftUI ที่ฝังใน UIKit ผ่าน UIHostingController วงจรชีวิตของออบเจ็กต์ผูกกับวิว SwiftUI ไม่ใช่ UIViewController ถ้าวิว SwiftUI ถูกแทนที่ @StateObject จะถูกทำลาย

อะไรดีกว่า: @StateObject หนึ่งตัวกับ ViewModel ใหญ่ หรือหลายตัวเล็ก?

@StateObject เล็ก ๆ หลายตัว ที่มีความรับผิดชอบแยกกัน สิ่งนี้ช่วยปรับปรุงความสามารถในการทดสอบ การใช้ซ้ำ และประสิทธิภาพ—เมื่อออบเจ็กต์หนึ่งเปลี่ยนแปลง เฉพาะส่วนที่สมัครรับของอินเทอร์เฟซเท่านั้นที่ถูกวาดใหม่ ไม่ใช่ทั้งวิว

สรุป

  • @StateObject — Property Wrapper สำหรับสร้างและเป็นเจ้าของ ObservableObject ใน view
  • อินสแตนซ์เดียว — ออบเจ็กต์ไม่ถูกสร้างใหม่เมื่อเรนเดอร์ body ซ้ำ
  • แหล่งความจริง — @StateObject ในวิวรากรับประกันความเสถียรของข้อมูลสำหรับลำดับชั้น
  • กฎการเลือก — @StateObject สำหรับสร้าง @ObservedObject สำหรับรับออบเจ็กต์สำเร็จรูป
  • การเริ่มต้น — พารามิเตอร์ใน @StateObject ถูกคำนวณครั้งเดียว การอัปเดตไม่ถูกติดตาม
  • Deinit — การล้างข้อมูลตัวจับเวลาและการสมัครรับใน deinit ของ ObservableObject ที่จำเป็น
  • iOS 14+ — @StateObject พร้อมใช้งานตั้งแต่ iOS 14, macOS 11, watchOS 7, tvOS 14

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม