
SwiftUI State Management: @State, @Binding, @ObservedObject và khi nào dùng cái nào
SwiftUI ra đời mang lại cách tiếp cận declarative hoàn toàn mới cho lập trình iOS. Thay vì ra lệnh cập nhật UI từng bước như UIKit, bạn mô tả “giao diện trông như thế nào khi state là gì” — và framework tự cập nhật khi state thay đổi. Tuy nhiên, chính sự linh hoạt này đặt ra câu hỏi: với bao nhiêu property wrapper khác nhau, bạn biết chọn cái nào cho tình huống nào?
Hiểu rõ SwiftUI state management không chỉ giúp code gọn hơn mà còn tránh bug khó debug do re-render không mong muốn. Bài viết này đi qua từng wrapper phổ biến, đưa ra ví dụ cụ thể và quy tắc chọn nhanh.

@State — State nội bộ, thuộc về view
@State dành cho giá trị thuộc sở hữu nội bộ của view. Khi giá trị thay đổi, SwiftUI tự động re-render view. Điểm quan trọng: @State không chia sẻ với bất kỳ view nào khác.
struct CounterView: View {
@State private var count = 0 // Chỉ dùng trong view này
var body: some View {
VStack {
Text("Count: (count)")
Button("+1") { count += 1 }
}
}
}
Quy tắc vàng: nếu dữ liệu chỉ cần cho một view và không cần truyền ra ngoài, dùng @State. Ví dụ: toggle hiển thị alert, giá trị slider nội bộ, trạng thái expanded/collapsed của một section.
@Binding — Truyền state xuống dưới, hai chiều
Khi bạn cần view con đọc và ghi state của view cha, dùng @Binding. Binding tạo “đường dây hai chiều” giữa hai view.
struct ParentView: View {
@State private var isOn = false
var body: some View {
ToggleView(isOn: $isOn) // $ tạo binding
}
}
struct ToggleView: View {
@Binding var isOn: Bool // Chỉ nhận, không sở hữu
var body: some View {
Toggle("Feature", isOn: $isOn)
}
}
Áp dụng: form row, custom slider, stepper, hay bất kỳ component nào cần phản ánh state từ cha. Lưu ý: binding không tạo ra sở hữu — nó chỉ là con trỏ tham chiếu.
@ObservedObject — Quan sát object tuân thủ ObservableObject
Khi state phức tạp hơn một giá trị đơn — ví dụ đối tượng network manager, user session, hay cart model — bạn cần @ObservedObject. Object phải tuân thủ protocol ObservableObject và phát hành thông báo qua @Published.

class CartModel: ObservableObject {
@Published var items: [Item] = []
@Published var total: Decimal = 0
}
struct CartView: View {
@ObservedObject var cart: CartModel // Nhận reference
var body: some View {
List(cart.items) { item in
Text(item.name)
}
}
}
Khi bất kỳ @Published property nào của CartModel thay đổi, tất cả view đang @ObservedObject nó sẽ re-render. Đây là pattern phổ biến cho view model trong kiến trúc MVVM.
@StateObject — Tạo và sở hữu object trong view
@StateObject ra đời để giải quyết vấn đề “ai tạo, ai sở hữu” với @ObservedObject. Dùng @StateObject khi view tạo ra object lần đầu tiên và giữ quyền sở hữu.
struct ProfileView: View {
@StateObject private var profile = ProfileModel() // View sở hữu
var body: some View {
// ...
}
}
Quy tắc thực tế: view tạo view model → dùng @StateObject. View chỉ nhận view model từ cha → dùng @ObservedObject. Nếu dùng nhầm, bạn có thể gặp tình trạng object bị tạo lại nhiều lần, mất dữ liệu tạm.
@EnvironmentObject — Chia sẻ toàn cục trong view hierarchy
Khi nhiều view cùng cần truy cập cùng một đối tượng — theme, authentication session, locale settings — bạn không muốn truyền qua 10 tầng view. @EnvironmentObject giải quyết bằng cách “inject” object vào environment và bất kỳ view nào cũng có thể đọc.
@main
struct MyApp: App {
@StateObject private var auth = AuthModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(auth) // Inject
}
}
}
struct DeepView: View {
@EnvironmentObject var auth: AuthModel // Đọc từ environment
// ...
}
Cảnh báo: nếu không có object tương ứng trong environment, app sẽ crash. Dùng environment cho data thực sự toàn cục, không lạm dụng cho mọi thứ.
Bảng so sánh nhanh
| Wrapper | Sở hữu | Chia sẻ | Dùng khi |
|---|---|---|---|
| @State | Có | Không | State nội bộ view đơn |
| @Binding | Không | Hai chiều với cha | View con cần đọc/ghi state cha |
| @ObservedObject | Không | Nhiều view | Nhận object từ cha |
| @StateObject | Có | Nhiều view | Tạo và giữ object trong view |
| @EnvironmentObject | Không | Toàn hierarchy | Data toàn cục như auth, theme |
Kết luận
SwiftUI state management không phải là “chọn cái nào mạnh nhất”, mà là “chọn cái nào phù hợp với ownership và scope”. Một view đơn giản chỉ cần @State; form phức tạp cần @Binding; screen có view model cần @StateObject hoặc @ObservedObject; và app-wide setting cần @EnvironmentObject. Áp dụng đúng wrapper giúp code tường minh, dễ test, và tránh lỗi re-render không mong muốn. Để hiểu sâu hơn về kiến trúc tổng thể, bạn có thể tham khảo Hacking with Swift — State System và tài liệu chính thức từ Apple SwiftUI Tutorials.
