@StateObject là một property wrapper trong SwiftUI tạo và sở hữu một phiên bản ObservableObject trong suốt vòng đời của một View. Khi một View xuất hiện lần đầu trên màn hình, @StateObject khởi tạo đối tượng và lưu trữ nó cho đến khi View bị xóa khỏi bộ nhớ. Điều này đảm bảo dữ liệu không bị đặt lại khi giao diện được xây dựng lại — ví dụ, khi thay đổi chủ đề hoặc cập nhật View cha. Theo Tài liệu Nhà phát triển Apple (2025), @StateObject nên được sử dụng như nguồn sự thật chính (source of truth) cho ObservableObject trong hệ thống phân cấp SwiftUI, trong khi các View con nhận đối tượng đã được tạo thông qua @ObservedObject hoặc @EnvironmentObject.
Các Điểm Chính
@StateObject là một property wrapper được giới thiệu trong iOS 14 cho phép View tạo và sở hữu một phiên bản của lớp tuân thủ giao thức ObservableObject. Không giống như @State hoạt động với kiểu giá trị (struct), @StateObject được thiết kế cho kiểu tham chiếu — các lớp có thể thông báo cho SwiftUI về các thay đổi thuộc tính của chúng.
Khi một View sử dụng @StateObject var viewModel: MyViewModel, SwiftUI tự động tạo một phiên bản MyViewModel khi View xuất hiện lần đầu và lưu trữ nó trong bộ nhớ đặc biệt của framework. Mỗi khi View được cập nhật (ví dụ, khi trạng thái cha thay đổi), SwiftUI không tạo lại đối tượng — nó sử dụng phiên bản hiện có cho đến khi View bị xóa khỏi hệ thống phân cấp.
Theo Apple WWDC Session 10137 (2024), @StateObject giải quyết vấn đề mất dữ liệu tồn tại trong iOS 13 khi các View bị xây dựng lại, buộc các nhà phát triển phải tạo ObservableObject trong View cha và truyền nó qua bộ khởi tạo. Điều này dẫn đến trùng lặp mã và nguy cơ vô tình tạo lại đối tượng.
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)
}
}
}
Cơ chế @StateObject dựa trên sự tích hợp của SwiftUI với framework Combine. Khi ObservableObject đánh dấu các thuộc tính của nó bằng thuộc tính @Published, SwiftUI tự động đăng ký thay đổi thông qua publisher được tích hợp trong giao thức ObservableObject. Khi một thuộc tính được xuất bản thay đổi, đối tượng gửi tín hiệu qua publisher objectWillChange, kích hoạt việc vẽ lại tất cả các View đang quan sát đối tượng này.
SwiftUI lưu trữ phiên bản ObservableObject trong bộ nhớ đặc biệt gắn với một phiên bản View cụ thể. Bộ nhớ này được tạo một lần trong lần hiển thị đầu tiên và tồn tại cho đến khi View bị hủy. Đây là lý do tại sao @StateObject đảm bảo sự ổn định tham chiếu — SwiftUI quản lý bộ nhớ tự động, không phụ thuộc vào bộ khởi tạo của View.
Theo objc.io — Thinking in SwiftUI (2025), cách triển khai nội bộ của @StateObject sử dụng cơ chế tương tự @State nhưng cho kiểu tham chiếu: SwiftUI tạo một lớp bọc (boxing) xung quanh đối tượng và quản lý vòng đời của nó thông qua bộ cấp phát riêng, được tối ưu hóa cho việc xây dựng lại hệ thống phân cấp View thường xuyên.
Sự khác biệt chính giữa @StateObject và @ObservedObject nằm ở ai sở hữu đối tượng. @StateObject tạo và lưu trữ đối tượng — nó là chủ sở hữu. @ObservedObject chỉ quan sát đối tượng được tạo ở nơi khác và được truyền qua bộ khởi tạo hoặc thuộc tính.
| Đặc điểm | @StateObject | @ObservedObject |
|---|---|---|
| Quyền sở hữu | Tạo và sở hữu đối tượng | Chỉ quan sát |
| Khởi tạo | Bên trong View qua init/mặc định | Bên ngoài, truyền qua tham số |
| Vòng đời | Gắn với vòng đời của View | Không được View kiểm soát |
| Tạo lại | Không được tạo lại khi cập nhật | Có thể được thay thế từ bên ngoài |
| Phiên bản iOS | iOS 14+ | iOS 13+ |
Quy tắc rất đơn giản: nếu View tạo ObservableObject — hãy sử dụng @StateObject. Nếu View chỉ nhận đối tượng đã được tạo từ cha — hãy sử dụng @ObservedObject. Vi phạm quy tắc này dẫn đến mất dữ liệu (nếu sử dụng @ObservedObject cho quyền sở hữu) hoặc tạo đối tượng quá mức (nếu sử dụng @StateObject cho việc quan sát).
@StateObject nên được sử dụng trong các View là nguồn sự thật cho một tập dữ liệu cụ thể. Các kịch bản điển hình bao gồm màn hình có view model riêng, màn hình gốc của ngăn xếp điều hướng và các trình bày phương thức quản lý trạng thái riêng của chúng.
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")
}
}
}
Khởi tạo @StateObject với tham số yêu cầu cú pháp đặc biệt, vì SwiftUI tự quản lý việc tạo đối tượng. Bạn không thể chỉ đơn giản truyền tham số cho bộ khởi tạo — bạn cần sử dụng một closure thoát hoặc phương thức factory riêng.
Theo Swift by Sundell (2024), cách tiếp cận sạch nhất là sử dụng phương thức factory hoặc closure mà SwiftUI sẽ gọi khi đối tượng được tạo lần đầu. Một cách tiếp cận khác là khởi tạo ObservableObject trong View cha và truyền nó qua @StateObject bằng bộ khởi tạo tiêu chuẩn.
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)
}
}
Điều quan trọng cần nhớ là bộ khởi tạo View với @StateObject nên sử dụng dấu gạch dưới trước tên thuộc tính (_viewModel) để truy cập vào property wrapper, không phải giá trị của nó. Đây là một mẫu Swift tiêu chuẩn để làm việc với property wrapper trong các bộ khởi tạo.
Lỗi phổ biến nhất là sử dụng @ObservedObject thay vì @StateObject cho View đáng lẽ phải sở hữu đối tượng. Trong trường hợp này, mỗi lần cha được xây dựng lại, đối tượng sẽ được tạo lại, dẫn đến mất tất cả dữ liệu đã tích lũy. Lỗi này đặc biệt nguy hiểm trong các hệ thống phân cấp phức tạp với NavigationStack hoặc TabView.
Để tránh những vấn đề này, hãy tuân theo một quy tắc đơn giản: một @StateObject cho mỗi nguồn sự thật. Nếu dữ liệu cần được chia sẻ giữa nhiều màn hình — hãy tạo @StateObject một lần trong View gốc và truyền qua @ObservedObject hoặc @EnvironmentObject cho các phần tử con.
// ❌ 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
}
Câu Hỏi Thường Gặp
@State hoạt động với kiểu giá trị (struct, chuỗi, số) và lưu trữ giá trị trực tiếp trong bộ nhớ SwiftUI. @StateObject hoạt động với kiểu tham chiếu — các lớp tuân thủ ObservableObject. @State phù hợp cho trạng thái cục bộ đơn giản, @StateObject cho các đối tượng phức tạp có logic và thuộc tính được xuất bản.
Không, @StateObject chỉ khả dụng từ iOS 14 trở lên. Đối với iOS 13, hãy sử dụng @ObservedObject và tạo ObservableObject trong View cha qua @State với quản lý vòng đời thủ công. Một giải pháp thay thế là sử dụng @State với struct thay vì class cho dữ liệu không yêu cầu ngữ nghĩa tham chiếu.
View con sẽ tạo bản sao riêng của ObservableObject, hoàn toàn độc lập với bản sao của cha. Các thay đổi trong một bản sẽ không ảnh hưởng đến bản kia. Điều này hầu như luôn là lỗi: hãy sử dụng @ObservedObject để nhận đối tượng từ cha và @StateObject chỉ để tạo đối tượng mới bên trong View.
Đối tượng bị hủy khi View đã tạo ra nó bị xóa hoàn toàn khỏi hệ thống phân cấp SwiftUI. Đối với màn hình trong NavigationStack, điều này xảy ra khi pop khỏi ngăn xếp điều hướng. Đối với cửa sổ phương thức — khi nó được đóng lại. Đối với TabView — khi chuyển tab, nếu View không được lưu vào bộ nhớ đệm.
Sử dụng init tùy chỉnh với quyền truy cập vào property wrapper qua dấu gạch dưới: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Mẫu này cho phép truyền bất kỳ tham số nào vào ObservableObject trong khi vẫn duy trì đảm bảo tạo đối tượng một lần trong suốt vòng đời của View.
Tổng Kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm