@StateObject: nó là gì, khác biệt với @ObservedObject và ví dụ

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

@StateObject là Property Wrapper trong SwiftUI dùng để tạo và sở hữu một thể hiện ObservableObject trực tiếp trong view. SwiftUI đảm bảo rằng đối tượng được khởi tạo một lần trong vòng đời của view và không bị tạo lại khi render lần sau. Theo Tài liệu Nhà phát triển Apple (2025), @StateObject được khuyến nghị cho các view gốc tạo nguồn dữ liệu. @StateObject là lựa chọn đúng đắn để sở hữu ObservableObject trong hệ phân cấp SwiftUI.

Điểm chính

  • @StateObject — Property Wrapper để tạo và sở hữu ObservableObject trong view
  • Thể hiện duy nhất — đối tượng được tạo một lần và không bị tạo lại khi render
  • Nguồn sự thật — @StateObject đảm bảo ổn định dữ liệu cho toàn bộ hệ phân cấp
  • Khác biệt với @ObservedObject — @ObservedObject không sở hữu đối tượng và có thể mất nó
  • View gốc — @StateObject được dùng trong view tạo đối tượng

@StateObject trong SwiftUI là gì?

@StateObject là Property Wrapper được giới thiệu trong SwiftUI 2.0 (iOS 14), kết hợp khả năng của @ObservedObject và @State. Giống @ObservedObject, nó đăng ký thay đổi của ObservableObject. Giống @State, nó đảm bảo dữ liệu sống sót qua các lần khởi tạo lặp lại của cấu trúc view. @StateObject tạo đối tượng một lần khi view xuất hiện lần đầu và lưu trữ nó trong vùng nhớ heap của SwiftUI.

Trước @StateObject, nhà phát triển dùng @ObservedObject cho tất cả ObservableObjects, bao gồm cả những đối tượng được tạo trong view. Điều này dẫn đến mất dữ liệu thường xuyên khi view cha được cập nhật, làm cho cấu trúc view bị tạo lại và mang theo thể hiện @ObservedObject. @StateObject đã giải quyết điều này bằng cách thêm một đảm bảo ổn định.

Quy tắc chính: @StateObject được dùng trong view tạo đối tượng trong bộ khởi tạo mặc định (let model = ViewModel()). Các view con nhận đối tượng này dùng @ObservedObject. Sự phân tách này đảm bảo một nguồn sự thật duy nhất trong toàn bộ hệ phân cấp.

Vòng đời của @StateObject

SwiftUI quản lý vòng đời của @StateObject thông qua trình quản lý lưu trữ tương tự @State. Khi view xuất hiện lần đầu, SwiftUI cấp phát bộ nhớ cho đối tượng và lưu nó trong vùng lưu trữ liên tục. Khi render lại (gọi body), đối tượng không bị tạo lại—thể hiện hiện tại được sử dụng. Đối tượng tồn tại chừng nào view còn trong hệ phân cấp.

Khi view bị xóa khỏi hệ phân cấp, SwiftUI hủy @StateObject, gọi deinit. Khi view được thêm lại vào hệ phân cấp, một thể hiện mới được tạo. Điều này quan trọng khi thiết kế: nếu bạn cần giữ dữ liệu giữa các lần xóa view, hãy dùng tầng dịch vụ (singleton hoặc DI) hoặc @AppStorage để duy trì.

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

Trong ví dụ, TimerViewModel được tạo qua @StateObject và tồn tại chừng nào TimerView còn trên màn hình. Timer khởi động trong onAppear và dừng trong deinit. Nếu dùng @ObservedObject, mỗi lần TimerView render lại sẽ tạo TimerViewModel mới với seconds = 0, và timer sẽ không bao giờ hoạt động chính xác. @StateObject đảm bảo viewModel là duy nhất và ổn định.

@StateObject vs @ObservedObject: so sánh

Lựa chọn giữa @StateObject và @ObservedObject phụ thuộc vào ai sở hữu đối tượng. Nếu view tạo đối tượng—@StateObject. Nếu view nhận đối tượng có sẵn—@ObservedObject. Quy tắc này quan trọng đến nỗi Xcode hiển thị cảnh báo khi sử dụng @StateObject trong view con nhận đối tượng qua bộ khởi tạo.

Tình huốngWrapper được khuyến nghị
View tạo model qua ViewModel()@StateObject
View nhận model từ cha@ObservedObject
Model được dùng trong một view@StateObject
Model được truyền qua Environment@EnvironmentObject
Model cần cho xem trước@ObservedObject + mock

Trong thực tế, khi bắt đầu dự án, @StateObject thường được dùng trong view gốc và @ObservedObject trong tất cả view con. Khi ứng dụng phát triển, một số thể hiện @StateObject có thể được thay thế bằng @EnvironmentObject để đơn giản hóa hệ phân cấp. Tuy nhiên, @StateObject vẫn là lựa chọn tốt nhất cho các màn hình mô-đun có logic riêng.

Các mẫu sử dụng @StateObject

Mẫu đầu tiên—MVVM với @StateObject. ViewModel dưới dạng ObservableObject được tạo trong view qua @StateObject. ViewModel chứa các thuộc tính @Published và logic nghiệp vụ. View đăng ký thay đổi và cập nhật giao diện. Cách tiếp cận này cung cấp sự cô lập có thể kiểm tra: ViewModel có thể được kiểm tra không cần UI bằng cách tạo thể hiện trực tiếp.

Mẫu thứ hai—@StateObject với phụ thuộc. Nếu ViewModel yêu cầu dịch vụ, hãy dùng khởi tạo với tham số. Ví dụ: @StateObject var viewModel = UserViewModel(api: APIClient.shared). Tuy nhiên hãy cẩn thận: tham số được tính mỗi lần render body, nhưng đối tượng chỉ được tạo một lần. SwiftUI bỏ qua các lần khởi tạo @StateObject sau đó.

Mẫu thứ ba—@StateObject lồng nhau. Trong SwiftUI, bạn có thể có nhiều @StateObject trong một view, nhưng điều này hiếm khi hợp lý. Thông thường một @StateObject xử lý toàn bộ tập dữ liệu của view. Nếu logic trở nên quá phức tạp, hãy chia nó thành sự kết hợp các dịch vụ @ObservedObject trong một @StateObject.

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

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

Trong ví dụ, AppView tạo hai @StateObject: NavigationRouter để quản lý điều hướng và AuthViewModel để xác thực. Cả hai đối tượng được tiêm vào Environment qua environmentObject. Bất kỳ view con nào cũng có thể truy cập chúng qua @EnvironmentObject mà không cần qua chuỗi bộ khởi tạo.

@StateObject và khởi tạo với tham số

@StateObject hỗ trợ khởi tạo với bất kỳ tham số nào, nhưng với một lưu ý quan trọng: bộ khởi tạo chỉ được gọi một lần. Khi render lại body sau đó, các giá trị tham số mới bị bỏ qua. Điều này có nghĩa là nếu bạn truyền @State var id: Int = 5 vào @StateObject var vm = ViewModel(id: id), khi id thay đổi, ViewModel sẽ không nhận được giá trị mới.

Để giải quyết vấn đề này, hãy dùng onReceive hoặc onAppear để đồng bộ hóa. Đăng ký thay đổi tham số trong ViewModel qua Combine hoặc truyền tham số qua phương thức .onChange(of:) ở cấp view. Một giải pháp thay thế là dùng @ObservedObject thay vì @StateObject nếu đối tượng cần phản ứng động với các thay đổi bên ngoài.

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

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

Cách tiếp cận đúng: DetailView nhận itemId như thuộc tính let (được truyền qua bộ khởi tạo của cấu trúc) và @StateObject tạo DetailViewModel không có tham số. Trong onAppear, phương thức load(id:) được gọi để tải dữ liệu cho ID đã truyền. Điều này đảm bảo ViewModel được tạo bởi cơ chế @StateObject, nhưng dữ liệu được tải mỗi khi view xuất hiện với ID hiện tại.

Lỗi thường gặp với @StateObject

Lỗi chính—dùng @StateObject trong view con nhận đối tượng từ cha. Nếu ParentView tạo @StateObject model và ChildView khai báo @StateObject var model: ModelType (với tham số mặc định), ChildView sẽ tạo thể hiện độc lập riêng. Đối tượng cha và con sẽ không được kết nối và thay đổi ở một bên sẽ không ảnh hưởng đến bên kia.

Lỗi thứ hai—đặt @StateObject trong List hoặc ForEach. Mỗi phần tử danh sách tạo @StateObject riêng, dẫn đến nhiều thể hiện độc lập. Cho danh sách, cách đúng là truyền một ObservableObject cho tất cả phần tử qua @ObservedObject hoặc dùng cấu trúc Identifiable với @State trong List.

Vấn đề thứ ba—thiếu dọn dẹp trong deinit. @StateObject tồn tại trong suốt vòng đời của view. Nếu đối tượng tạo timer, đăng ký Combine hoặc yêu cầu mạng, deinit phải hủy chúng. Nếu không, rò rỉ bộ nhớ và tiếp tục chạy nền sau khi đóng màn hình là không thể tránh khỏi. Luôn dùng Combine Cancellable store hoặc hủy timer trong deinit.

Câu hỏi thường gặp

@StateObject được giới thiệu trong SwiftUI khi nào?

@StateObject được thêm vào SwiftUI 2.0 tại WWDC 2020 cùng với iOS 14, macOS 11, watchOS 7 và tvOS 14. Trước đó, @ObservedObject là cách duy nhất để làm việc với ObservableObject, thường dẫn đến lỗi mất dữ liệu.

@StateObject có thể là tùy chọn không?

Không, @StateObject không hỗ trợ kiểu Optional. Đối tượng phải được khởi tạo khi khai báo. Nếu bạn cần một đối tượng tùy chọn, hãy dùng @ObservedObject hoặc @EnvironmentObject với kiểu tùy chọn.

Làm thế nào để xác minh @StateObject chỉ được tạo một lần?

Thêm print(#function) vào bộ khởi tạo và deinit của ObservableObject. Nếu init không được gọi khi render lại—@StateObject hoạt động chính xác. Nếu init được gọi mỗi lần—hãy thay @ObservedObject bằng @StateObject.

Có thể dùng @StateObject với UIKit qua UIHostingController không?

Có, @StateObject hoạt động trong các view SwiftUI được nhúng trong UIKit qua UIHostingController. Vòng đời đối tượng gắn với view SwiftUI, không phải UIViewController. Nếu view SwiftUI bị thay thế, @StateObject bị hủy.

Điều gì tốt hơn: một @StateObject với ViewModel lớn hay nhiều cái nhỏ?

Nhiều @StateObject nhỏ với trách nhiệm riêng biệt. Điều này cải thiện khả năng kiểm tra, tái sử dụng và hiệu suất—khi một đối tượng thay đổi, chỉ các phần đăng ký của giao diện được vẽ lại, không phải toàn bộ view.

Tóm tắt

  • @StateObject — Property Wrapper để tạo và sở hữu ObservableObject trong view
  • Thể hiện duy nhất — đối tượng không bị tạo lại khi render body sau đó
  • Nguồn sự thật — @StateObject trong view gốc đảm bảo ổn định dữ liệu cho hệ phân cấp
  • Quy tắc chọn — @StateObject để tạo, @ObservedObject để nhận đối tượng có sẵn
  • Khởi tạo — tham số trong @StateObject được tính một lần, cập nhật không được theo dõi
  • Deinit — dọn dẹp bắt buộc timer và đăng ký trong deinit của ObservableObject
  • iOS 14+ — @StateObject có sẵn từ iOS 14, macOS 11, watchOS 7, tvOS 14

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