@Published: định nghĩa, nguyên lý hoạt động và ứng dụng

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

@Published là một property wrapper từ framework Combine, tự động công bố các thay đổi của thuộc tính lớp tuân thủ giao thức ObservableObject. Khi giá trị của thuộc tính được đánh dấu @Published thay đổi, SwiftUI nhận được tín hiệu qua objectWillChange và vẽ lại tất cả các view đã đăng ký theo dõi đối tượng đó. Theo tài liệu Apple Combine Framework (2025), @Published tạo ra một Publisher có thể được biến đổi thêm qua các toán tử Combine: map, filter, debounce và nhiều toán tử khác. Điều này làm cho @Published trở thành cầu nối chính giữa dữ liệu và giao diện người dùng trong kiến trúc MVVM.

Những điểm chính

  • @Published — property wrapper để tự động công bố các thay đổi thuộc tính ObservableObject trong SwiftUI và Combine
  • Cơ chế: khi giá trị thay đổi, objectWillChange được gọi, kích hoạt việc vẽ lại các view đã đăng ký
  • Publisher có thể truy cập qua phép chiếu $property — có thể đăng ký, kết hợp và biến đổi luồng dữ liệu
  • ObservedObjectStateObject tự động đăng ký theo dõi các thuộc tính @Published — không cần đăng ký thủ công
  • iOS 17+ macro @Observable cung cấp một giải pháp thay thế, nhưng @Published vẫn là tiêu chuẩn cho các pipeline Combine

@Published là gì?

@Published là một property wrapper được định nghĩa trong mô-đun Combine, thêm khả năng tự động thông báo cho người đăng ký về các thay đổi của thuộc tính lớp. Nó chỉ có thể được sử dụng bên trong một lớp (không phải cấu trúc) và chỉ trên các thuộc tính của lớp tuân thủ giao thức ObservableObject.

Khi giá trị của thuộc tính @Published thay đổi, Combine tạo ra một sự kiện thông qua một publisher tích hợp có thể truy cập qua tiền tố dollar: $propertyName. Publisher này là một ObservableObjectPublisher, thuộc về chính ObservableObject. SwiftUI tự động đăng ký theo dõi nó khi một view sử dụng @ObservedObject hoặc @StateObject, và vẽ lại view bất cứ khi nào bất kỳ thuộc tính @Published nào trong đối tượng thay đổi.

Theo cuốn sách của Matt Neuburg “IOS 18 Programming Fundamentals with Swift” (2025), @Published là một wrapper tiện lợi trên mẫu willSet, tự động gọi objectWillChange.send(). Trên thực tế, trình biên dịch mở rộng @Published thành một computed property với bộ quan sát willSet, mang lại chi phí runtime bằng không so với triển khai thủ công.

Sử dụng @Published cho tất cả các thuộc tính ObservableObject mà các thay đổi của chúng cần được phản ánh trong giao diện. Đối với các thuộc tính không ảnh hưởng đến UI, các stored property thông thường không có @Published giúp giảm việc vẽ lại không cần thiết.

Cách @Published hoạt động

@Published tạo ra hai yếu tố chính tại thời điểm biên dịch. Yếu tố thứ nhất là một stored property với bộ quan sát willSet gọi objectWillChange.send() trước khi ghi giá trị mới. Yếu tố thứ hai là phép chiếu $propertyName, trả về một Published.Publisher có thể được sử dụng trực tiếp trong các pipeline Combine.

Hãy xem xét lớp Settings với ba thuộc tính: hai @Published và một thuộc tính thông thường:

swift
class Settings: ObservableObject {
    @Published var username: String = "Guest"
    @Published var isDarkMode = false
    var lastLogin: Date = Date()  // without @Published
}

Khi username hoặc isDarkMode thay đổi, SwiftUI sẽ vẽ lại tất cả các view đã đăng ký theo dõi thể hiện Settings. Việc thay đổi lastLogin sẽ không kích hoạt vẽ lại. Nếu bạn cần thông báo thủ công cho người đăng ký về một thay đổi trên thuộc tính thông thường, bạn có thể gọi objectWillChange.send() trong bộ quan sát willSet.

Một chi tiết quan trọng: @Published chỉ công bố các thay đổi khi một thuộc tính được gán trực tiếp. Nếu thuộc tính là kiểu tham chiếu (lớp) và trạng thái bên trong của nó thay đổi mà không thay thế tham chiếu, @Published sẽ không phát hiện ra điều đó. Trong những trường hợp như vậy, cần phải gửi sự kiện thủ công hoặc chuyển sang kiểu giá trị (cấu trúc).

@Published và Combine

@Published được tích hợp chặt chẽ với Combine — mỗi thuộc tính @Published tự động cung cấp một publisher có thể truy cập qua phép chiếu $propertyName. Điều này cho phép sử dụng các toán tử Combine để lọc, biến đổi, kết hợp và xử lý giá trị trì hoãn.

Một kịch bản điển hình là tìm kiếm với debounce. Trường nhập liệu được liên kết với thuộc tính @Published searchText, nhưng yêu cầu đến máy chủ chỉ nên được gửi sau khi tạm dừng 300 ms. Combine với $searchText.debounce giải quyết vấn đề này trong một dòng:

swift
class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [String] = []
    private var cancellables = Set<AnyCancellable>()

    init() {
        setupSearchSubscription()
    }

    private func setupSearchSubscription() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    private func performSearch(_ text: String) { }
}

Theo bài viết của John Sundell (Swift by Sundell, 2024), kết hợp @Published với Combine là một mẫu tiêu chuẩn cho các pipeline phản ứng trong ứng dụng SwiftUI: xác thực, debounce, throttle, combineLatest, merge với các publisher khác. @Published hoạt động như một cầu nối giữa mã UI mệnh lệnh và Combine phản ứng.

@Published vs macro @Observable

Với việc phát hành iOS 17, Apple đã giới thiệu macro @Observable, cung cấp một cách tiếp cận thay thế cho tính phản ứng mà không cần ObservableObject và @Published. @Observable tự động theo dõi việc truy cập thuộc tính ở cấp độ đọc thay vì ghi, cung cấp việc vẽ lại chính xác hơn — chỉ view đọc thuộc tính đã thay đổi cụ thể mới được cập nhật.

Tuy nhiên, điều này không có nghĩa là @Published đã bị lỗi thời. @Published vẫn cần thiết khi cần tích hợp với các pipeline Combine — phép chiếu $propertyName cung cấp một publisher mà @Observable không có. Ngoài ra, để tương thích ngược với iOS 16 trở xuống, @Published+ObservableObject là lựa chọn duy nhất. Theo phiên Apple WWDC 2023 “Discover Observation in SwiftUI,” Apple khuyến nghị @Observable cho các dự án mới, nhưng vẫn duy trì hỗ trợ rõ ràng cho @Published đối với mã hiện có và các kịch bản Combine.

Trong thực tế, nhiều dự án sử dụng cách tiếp cận kết hợp: các mô hình dữ liệu mới sử dụng @Observable, trong khi các ObservableObject hiện có với @Published vẫn được giữ nguyên mà không tái cấu trúc. @Published cũng không thể thiếu khi cần kiểm soát chi tiết việc công bố — ví dụ, trì hoãn thông báo cho đến khi hoàn tất cập nhật hàng loạt nhiều thuộc tính.

Các lỗi thường gặp với @Published

Lỗi đầu tiên là sử dụng @Published trong cấu trúc. Trình biên dịch sẽ báo lỗi: “Property wrapper cannot be applied to a computed property” hoặc “‘@Published’ is only available on members of a class.” @Published yêu cầu ngữ nghĩa tham chiếu vì ObservableObjectPublisher là một lớp phải là duy nhất cho mỗi thể hiện.

Lỗi thứ hai là thay đổi nội dung của thuộc tính tham chiếu mà không thay thế tham chiếu. Nếu thuộc tính @Published có kiểu mảng [String] và bạn gọi array.append("moi"), @Published sẽ không phát hiện thay đổi vì tham chiếu đến mảng không thay đổi. Giải pháp: gán giá trị mới cho thuộc tính array = array + ["moi"] hoặc sử dụng objectWillChange.send() thủ công.

Lỗi thứ ba là số lượng quá nhiều thuộc tính @Published. Mỗi thuộc tính @Published kích hoạt việc vẽ lại tất cả các view đã đăng ký theo dõi ObservableObject, không chỉ những view đọc thuộc tính đó. Theo Point-Free (2025), chia một ObservableObject lớn thành nhiều ObservableObject nhỏ hơn với @StateObject và @EnvironmentObject giúp giảm việc vẽ lại không cần thiết và cải thiện hiệu suất.

Ví dụ mã

Ví dụ đầu tiên là ViewModel của biểu mẫu đăng ký với xác thực. Các thuộc tính @Published emailpassword kích hoạt hiển thị lỗi xác thực qua pipeline Combine:

swift
class RegistrationViewModel: ObservableObject {
    @Published var email = ""
    @Published var password = ""
    @Published var emailError: String?
    @Published var isFormValid = false
    private var cancellables = Set<AnyCancellable>()

    init() {
        $email
            .map { $0.contains("@") ? nil : "Invalid email" }
            .assign(to: &$emailError)
            .store(in: &cancellables)

        $email.combineLatest($password)
            .map { !$0.isEmpty && !$1.isEmpty }
            .assign(to: &$isFormValid)
            .store(in: &cancellables)
    }
}

Ví dụ thứ hai là công bố thủ công cho một tập hợp các phần tử tham chiếu. Thay vì thay thế toàn bộ mảng mỗi khi có thay đổi trong một phần tử, objectWillChange.send() được sử dụng:

swift
class TodoItem {
    var title: String
    var isDone = false
    init(title: String) { self.title = title }
}

class TodoListViewModel: ObservableObject {
    @Published var items: [TodoItem] = []

    func toggle(item: TodoItem) {
        item.isDone.toggle()
        self.objectWillChange.send()  // manual notification
    }
}

Ví dụ thứ ba là Assign đến thuộc tính @Published thông qua Combine. Sử dụng cú pháp Swift 5.9 mới, bạn có thể gán trực tiếp qua phép chiếu assign(to: &$property) mà không cần wrapper Optional. Đây là cách ngắn nhất để kết nối một publisher với thuộc tính @Published mà không tạo đăng ký.

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

Có thể sử dụng @Published trong cấu trúc không?

Không, @Published chỉ có thể được sử dụng bên trong một lớp tuân thủ ObservableObject. Trong cấu trúc, hãy sử dụng @State cho trạng thái cục bộ hoặc @Bindable với macro @Observable trong iOS 17+. Cố gắng sử dụng @Published trong cấu trúc sẽ gây ra lỗi biên dịch.

@Published hoạt động thế nào với mảng và từ điển?

Cách tiếp cận đúng: gán giá trị mới hoàn toàn (array = array + ["moi"]). @Published theo dõi việc thay thế tham chiếu, không phải thay đổi nội dung. Đối với tập hợp các kiểu tham chiếu, hãy sử dụng lệnh gọi thủ công objectWillChange.send() sau khi thay đổi trạng thái bên trong của các phần tử.

@Published khác @State như thế nào?

@State được thiết kế cho trạng thái cục bộ trong một view duy nhất và chỉ hoạt động với các kiểu giá trị. @Published dành cho các thuộc tính ObservableObject có thể được đọc bởi nhiều view qua @ObservedObject hoặc @EnvironmentObject. @State đơn giản hơn, @Published mạnh mẽ hơn nhờ tích hợp với Combine.

Có cần @Published cho mọi thuộc tính ObservableObject không?

Chỉ những thuộc tính mà các thay đổi của chúng cần cập nhật UI. Các thuộc tính cho tính toán nội bộ, bộ nhớ đệm hoặc cờ tạm thời không cần @Published — điều này giúp giảm việc vẽ lại không cần thiết. Hãy sử dụng @Published như một tín hiệu rằng “thuộc tính này quan trọng cho giao diện.”

@Published hoạt động thế nào với Core Data?

SwiftUI tích hợp với Core Data qua @FetchRequest@ObservedObject cho NSManagedObject. ManagedObject đã tuân thủ ObservableObject, do đó @Published không cần thiết — NSManagedObject tự thông báo các thay đổi. @Published được sử dụng trong lớp ViewModel giữa Core Data và UI để biến đổi dữ liệu.

Tóm tắt

  • @Published — property wrapper từ Combine tự động công bố các thay đổi thuộc tính ObservableObject cho SwiftUI và các pipeline Combine
  • Cơ chế: bộ quan sát willSet gọi objectWillChange.send(), tạo ra publisher qua phép chiếu $property
  • Combine: @Published cung cấp publisher cho debounce, map, combineLatest và các toán tử khác — cầu nối giữa UI và các pipeline phản ứng
  • @Observable (iOS 17+) — giải pháp thay thế cho các dự án mới, nhưng @Published vẫn là tiêu chuẩn cho Combine và tương thích ngược
  • Lỗi: @Published không hoạt động trong cấu trúc, không theo dõi thay đổi kiểu tham chiếu, quá nhiều thuộc tính @Published làm tăng việc vẽ lại
  • Thực hành tốt nhất: chỉ đánh dấu các thuộc tính ảnh hưởng đến UI với @Published, chia ObservableObject lớn thành nhiều cái nhỏ hơn
  • Assign: assign(to: &$property) trong Swift 5.9 cho phép đăng ký publisher trực tiếp vào thuộc tính @Published

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