@StateObject: nó là gì, tạo và quản lý ObservableObject

Tác giả: IT Sectr Đã đăng: 2026-06-26 Thời gian đọc: 9 phút

@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à property wrapper để tạo và sở hữu một ObservableObject bên trong View.
  • Tạo một lần — đối tượng được khởi tạo một lần trong suốt vòng đời của View và không được tạo lại khi xây dựng lại.
  • Nguồn sự thật — @StateObject là nguồn sự thật trong hệ thống phân cấp, không giống như @ObservedObject.
  • Vòng đời — đối tượng tồn tại miễn là View còn trong bộ nhớ và bị hủy cùng với nó.
  • Khởi tạo — @StateObject yêu cầu giá trị ban đầu khi tạo, thường qua init với tham số.

@StateObject trong SwiftUI là gì

@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.

swift
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 hoạt động

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.

Vòng đời của @StateObject

  • Tạo — khi View xuất hiện lần đầu trên màn hình, SwiftUI gọi bộ khởi tạo của đối tượng và lưu trữ tham chiếu.
  • Xây dựng lại — khi View cha được cập nhật, đối tượng không được tạo lại; phiên bản hiện có được sử dụng.
  • Hủy — khi View rời khỏi màn hình và bị xóa khỏi hệ thống phân cấp, SwiftUI gọi deinit của đối tượng.

@StateObject vs @ObservedObject: khác biệt chính

Sự khác biệt chính giữa @StateObject@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ữuTạo và sở hữu đối tượngChỉ quan sát
Khởi tạoBên trong View qua init/mặc địnhBên ngoài, truyền qua tham số
Vòng đờiGắn với vòng đời của ViewKhông được View kiểm soát
Tạo lạiKhông được tạo lại khi cập nhậtCó thể được thay thế từ bên ngoài
Phiên bản iOSiOS 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).

Khi nào sử dụng @StateObject

@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.

  • Màn hình có view model — mỗi màn hình quản lý dữ liệu và logic riêng nên tạo view model của nó qua @StateObject.
  • View gốc — trong hệ thống phân cấp NavigationStack hoặc TabView, phần tử gốc tạo dữ liệu và các phần tử con nhận dữ liệu qua @ObservedObject.
  • Cửa sổ phương thức — .sheet và .fullScreenCover thường yêu cầu @StateObject riêng để quản lý biểu mẫu hoặc quy trình.
  • Danh sách có thể chỉnh sửa — mỗi hàng danh sách chứa biểu mẫu chỉnh sửa nên có @StateObject riêng.
swift
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ố

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.

swift
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 thường gặp với @StateObject

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.

  • Mất dữ liệu khi điều hướng — nếu màn hình con sử dụng @ObservedObject cho view model riêng của nó, dữ liệu sẽ bị đặt lại khi quay lại và mở lại.
  • Rò rỉ bộ nhớ — tạo @StateObject trong View cha không bao giờ bị xóa có thể dẫn đến tích tụ đối tượng nếu mỗi màn hình con cũng tạo @StateObject mà không kiểm soát.
  • Trùng lặp đối tượng — truyền một ObservableObject duy nhất cho nhiều @StateObject trong các View khác nhau tạo ra nhiều phiên bản độc lập không đồng bộ với nhau.

Để 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.

swift
// ❌ 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

Sự khác biệt giữa @StateObject và @State là gì?

@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.

Tôi có thể sử dụng @StateObject trong iOS 13 không?

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.

Điều gì xảy ra nếu tôi sử dụng @StateObject trong View con nơi đối tượng được truyền từ cha?

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.

Khi nào đối tượng được tạo qua @StateObject bị hủy?

Đố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.

Làm thế nào để truyền tham số cho @StateObject khi khởi tạo?

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

  • @StateObject — property wrapper để tạo và sở hữu ObservableObject bên trong View, khả dụng từ iOS 14.
  • Đảm bảo tạo một lần — đối tượng được khởi tạo một lần và không được tạo lại khi View được xây dựng lại.
  • Nguồn sự thật — @StateObject là nguồn sự thật, trong khi @ObservedObject chỉ là người quan sát.
  • Vòng đời — đối tượng tồn tại miễn là View còn trong hệ thống phân cấp SwiftUI và bị hủy khi rời khỏi.
  • Khởi tạo với tham số — yêu cầu truy cập property wrapper qua _viewModel và StateObject(wrappedValue:).
  • Lỗi sở hữu — sử dụng @ObservedObject để tạo đối tượng dẫn đến mất dữ liệu khi xây dựng lại.
  • Một đối tượng — một @StateObject — cho dữ liệu chia sẻ, tạo @StateObject trong View gốc và truyền cho con qua @ObservedObject.

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.

Thảo luận dự án

Đọc thêm