@StateObject คือ property wrapper ใน SwiftUI ที่สร้างและเป็นเจ้าของอินสแตนซ์ ObservableObject ตลอดวงจรชีวิตทั้งหมดของ View เมื่อ View ปรากฏบนหน้าจอเป็นครั้งแรก @StateObject จะเริ่มต้นวัตถุและเก็บไว้จนกว่า View จะถูกลบออกจากหน่วยความจำ สิ่งนี้รับประกันว่าข้อมูลจะไม่ถูกรีเซ็ตเมื่อสร้างอินเทอร์เฟซใหม่ — ตัวอย่างเช่น เมื่อเปลี่ยนธีมหรืออัปเดต View หลัก ตามเอกสารของนักพัฒนา Apple (2025) ควรใช้ @StateObject เป็นแหล่งความจริงหลัก (source of truth) สำหรับ ObservableObject ในลำดับชั้น SwiftUI ในขณะที่ View ย่อยจะรับวัตถุที่สร้างไว้แล้วผ่าน @ObservedObject หรือ @EnvironmentObject
ประเด็นสำคัญ
@StateObject คือ property wrapper ที่ถูกนำมาใช้ใน iOS 14 ซึ่งช่วยให้ View สามารถสร้างและเป็นเจ้าของอินสแตนซ์ของคลาสที่สอดคล้องกับโปรโตคอล ObservableObject แตกต่างจาก @State ที่ทำงานกับชนิดค่า (struct) @StateObject ถูกออกแบบมาสำหรับชนิดอ้างอิง — คลาสที่สามารถแจ้ง SwiftUI เกี่ยวกับการเปลี่ยนแปลงของคุณสมบัติของพวกมัน
เมื่อ View ใช้ @StateObject var viewModel: MyViewModel SwiftUI จะสร้างอินสแตนซ์ของ MyViewModel โดยอัตโนมัติเมื่อ View ปรากฏครั้งแรกและเก็บไว้ในพื้นที่เก็บข้อมูลพิเศษของเฟรมเวิร์ก ในการอัปเดต View แต่ละครั้ง (ตัวอย่างเช่น เมื่อสถานะหลักเปลี่ยนแปลง) SwiftUI จะไม่สร้างวัตถุใหม่ — มันใช้อินสแตนซ์ที่มีอยู่จนกว่า View จะถูกลบออกจากลำดับชั้น
ตาม Apple WWDC Session 10137 (2024) @StateObject แก้ปัญหาการสูญเสียข้อมูลที่มีอยู่ใน iOS 13 เมื่อ Views ถูกสร้างใหม่ ซึ่งบังคับให้นักพัฒนาต้องสร้าง ObservableObject ใน View หลักและส่งผ่านตัวเริ่มต้น ทำให้เกิดการซ้ำซ้อนของโค้ดและความเสี่ยงในการสร้างวัตถุซ้ำโดยไม่ได้ตั้งใจ
import SwiftUI
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
Button("Increment", action: viewModel.increment)
}
}
}
กลไกของ @StateObject ขึ้นอยู่กับการรวมตัวของ SwiftUI กับเฟรมเวิร์ก Combine เมื่อ ObservableObject ทำเครื่องหมายคุณสมบัติของมันด้วยแอตทริบิวต์ @Published SwiftUI จะสมัครรับการเปลี่ยนแปลงโดยอัตโนมัติผ่านผู้เผยแพร่ที่สร้างไว้ในโปรโตคอล ObservableObject เมื่อคุณสมบัติที่เผยแพร่เปลี่ยนแปลง วัตถุจะส่งสัญญาณผ่านผู้เผยแพร่ objectWillChange ซึ่งกระตุ้นให้เกิดการวาดใหม่ของ View ทั้งหมดที่กำลังสังเกตวัตถุนี้
SwiftUI เก็บอินสแตนซ์ ObservableObject ไว้ในพื้นที่เก็บข้อมูลพิเศษที่ผูกกับอินสแตนซ์ View เฉพาะ พื้นที่จัดเก็บนี้ถูกสร้างขึ้นครั้งเดียวในระหว่างการเรนเดอร์ครั้งแรกและคงอยู่จนกว่า View จะถูกทำลาย นี่คือสาเหตุที่ @StateObject รับประกันความเสถียรของการอ้างอิง — SwiftUI จัดการหน่วยความจำโดยอัตโนมัติ โดยไม่ต้องพึ่งพาตัวเริ่มต้นของ View
ตาม objc.io — Thinking in SwiftUI (2025) การทำงานภายในของ @StateObject ใช้กลไกคล้ายกับ @State แต่สำหรับชนิดอ้างอิง: SwiftUI สร้าง wrapper หุ้มรอบวัตถุและจัดการวงจรชีวิตผ่านตัวจัดสรรของตัวเอง ซึ่งปรับให้เหมาะสมสำหรับการสร้างลำดับชั้น View บ่อยครั้ง
ความแตกต่างหลักระหว่าง @StateObject และ @ObservedObject อยู่ที่ใครเป็นเจ้าของวัตถุ @StateObject สร้างและเก็บวัตถุ — มันเป็นเจ้าของ @ObservedObject เพียงสังเกตวัตถุที่ถูกสร้างขึ้นที่อื่นและส่งผ่านตัวเริ่มต้นหรือคุณสมบัติ
| ลักษณะ | @StateObject | @ObservedObject |
|---|---|---|
| ความเป็นเจ้าของ | สร้างและเป็นเจ้าของวัตถุ | สังเกตเพียงอย่างเดียว |
| การเริ่มต้น | ภายใน View ผ่าน init/ค่าเริ่มต้น | ภายนอก ส่งผ่านพารามิเตอร์ |
| วงจรชีวิต | ผูกกับวงจรชีวิตของ View | ไม่ได้ถูกควบคุมโดย View |
| การสร้างใหม่ | ไม่ถูกสร้างใหม่เมื่ออัปเดต | สามารถถูกแทนที่จากภายนอก |
| เวอร์ชัน iOS | iOS 14+ | iOS 13+ |
กฎนั้นง่าย: ถ้า View สร้าง ObservableObject — ใช้ @StateObject ถ้า View เพียง รับ วัตถุที่สร้างไว้แล้วจากหลัก — ใช้ @ObservedObject การละเมิดกฎนี้จะนำไปสู่การสูญเสียข้อมูล (ถ้าใช้ @ObservedObject สำหรับความเป็นเจ้าของ) หรือการสร้างวัตถุมากเกินไป (ถ้าใช้ @StateObject สำหรับการสังเกต)
ควรใช้ @StateObject ใน View ที่เป็นแหล่งความจริงสำหรับชุดข้อมูลเฉพาะ สถานการณ์ทั่วไปรวมถึงหน้าจอที่มี view model ของตัวเอง หน้าจอรูทของสแต็กนำทาง และการนำเสนอแบบโมดอลที่จัดการสถานะของตัวเอง
struct ProfileView: View {
@StateObject var viewModel = ProfileViewModel()
var body: some View {
NavigationStack {
Form {
TextField("Name", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
Button("Save") {
viewModel.saveProfile()
}
}
.navigationTitle("Profile")
}
}
}
การเริ่มต้น @StateObject ด้วยพารามิเตอร์ต้องใช้ไวยากรณ์พิเศษ เนื่องจาก SwiftUI จัดการการสร้างวัตถุด้วยตัวเอง คุณไม่สามารถส่งพารามิเตอร์ไปยังตัวเริ่มต้นได้โดยตรง — คุณต้องใช้ closure แบบหลบหนีหรือวิธีการสร้างแยกต่างหาก
ตาม Swift by Sundell (2024) วิธีที่สะอาดที่สุดคือการใช้วิธีการสร้างหรือ closure ที่ SwiftUI จะเรียกเมื่อวัตถุถูกสร้างครั้งแรก วิธีทางเลือกคือการเริ่มต้น ObservableObject ใน View หลักและส่งผ่าน @StateObject โดยใช้ตัวเริ่มต้นมาตรฐาน
class UserViewModel: ObservableObject {
@Published var user: User
init(user: User) {
self.user = user
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserViewModel
init(user: User) {
_viewModel = StateObject(wrappedValue: UserViewModel(user: user))
}
var body: some View {
Text(viewModel.user.name)
}
}
สิ่งสำคัญที่ต้องจำคือ ตัวเริ่มต้น View ที่มี @StateObject ควรใช้ขีดล่างก่อนชื่อคุณสมบัติ (_viewModel) เพื่อเข้าถึง property wrapper เอง ไม่ใช่ค่าของมัน นี่เป็นรูปแบบ Swift มาตรฐานสำหรับการทำงานกับ property wrappers ในตัวเริ่มต้น
ข้อผิดพลาดที่พบบ่อยที่สุดคือการใช้ @ObservedObject แทน @StateObject สำหรับ View ที่ควรเป็นเจ้าของวัตถุ ในกรณีนี้ ทุกครั้งที่หลักถูกสร้างใหม่ วัตถุจะถูกสร้างขึ้นใหม่ ซึ่งนำไปสู่การสูญเสียข้อมูลที่สะสมทั้งหมด ข้อผิดพลาดนี้ร้ายกาจเป็นพิเศษในลำดับชั้นที่ซับซ้อนกับ NavigationStack หรือ TabView
เพื่อหลีกเลี่ยงปัญหาเหล่านี้ ให้ปฏิบัติตามกฎง่ายๆ: หนึ่ง @StateObject ต่อแหล่งความจริงหนึ่งแหล่ง ถ้าข้อมูลควรถูกใช้ร่วมกันหลายหน้าจอ — สร้าง @StateObject ครั้งเดียวใน View รูทและส่งผ่าน @ObservedObject หรือ @EnvironmentObject ไปยังองค์ประกอบย่อย
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
@ObservedObject var vm = ViewModel() // will be recreated on each update!
}
// ✅ Correct: @StateObject for owning
struct GoodView: View {
@StateObject var vm = ViewModel() // created once for View lifetime
}
คำถามที่พบบ่อย
@State ทำงานกับชนิดค่า (struct, สตริง, ตัวเลข) และเก็บค่าโดยตรงในพื้นที่จัดเก็บ SwiftUI @StateObject ทำงานกับชนิดอ้างอิง — คลาสที่สอดคล้องกับ ObservableObject @State เหมาะสำหรับสถานะท้องถิ่นที่เรียบง่าย @StateObject สำหรับวัตถุที่ซับซ้อนที่มีตรรกะและคุณสมบัติที่เผยแพร่
ไม่ @StateObject พร้อมใช้งานเฉพาะ iOS 14 ขึ้นไป สำหรับ iOS 13 ให้ใช้ @ObservedObject และสร้าง ObservableObject ใน View หลักผ่าน @State ด้วยการจัดการวงจรชีวิตด้วยตนเอง ทางเลือกคือใช้ @State กับ struct แทน class สำหรับข้อมูลที่ไม่ต้องการความหมายของการอ้างอิง
View ย่อย จะสร้างสำเนาของตัวเอง ของ ObservableObject ซึ่งเป็นอิสระจากของหลักโดยสมบูรณ์ การเปลี่ยนแปลงในอันหนึ่งจะไม่ส่งผลต่ออีกอันหนึ่ง นี่เกือบจะเป็นข้อผิดพลาดเสมอ: ใช้ @ObservedObject เพื่อรับวัตถุจากหลักและ @StateObject เฉพาะเมื่อสร้างวัตถุใหม่ภายใน View
วัตถุจะถูกทำลายเมื่อ View ที่สร้างมัน ถูกลบออกจากลำดับชั้น SwiftUI โดยสมบูรณ์ สำหรับหน้าจอใน NavigationStack สิ่งนี้เกิดขึ้นเมื่อ pop จากสแต็กนำทาง สำหรับหน้าต่างโมดอล — เมื่อถูกปิด สำหรับ TabView — เมื่อเปลี่ยนแท็บ ถ้า View ไม่ได้ถูกแคช
ใช้ init ที่กำหนดเองพร้อมการเข้าถึง property wrapper ผ่านขีดล่าง: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)) รูปแบบนี้ช่วยให้ส่งพารามิเตอร์ใดๆ ไปยัง ObservableObject ในขณะที่ยังคงรับประกันการสร้างวัตถุเพียงครั้งเดียวตลอดอายุของ View
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม