@Published:是什么、原理及应用

作者: IT Sectr 发布日期: 2026-06-19 阅读时间: 8 分钟

@Published — 是来自 Combine 框架的 property wrapper,能够自动发布遵宪 ObservableObject 协议的类的属性变更。当标记为 @Published 的属性值发生变化时,SwiftUI 通过 objectWillChange 接收到信号,并重新绘制所有订阅此对象的 view。据 Apple Combine Framework Documentation (2025),@Published 生成一个 Publisher,可以通过 Combine 运算符(map、filter、debounce 等)进一步转换。这使得 @Published 成为 MVVM 架构中数据与用户界面之间的关键桥梁。

核心要点

  • @Published — 用于在 SwiftUI 和 Combine 中自动发布 ObservableObject 属性变更的 property wrapper
  • 机制:值变更时会调用 objectWillChange,从而触发订阅的 view 重新绘制
  • Publisher 通过投影 $property 访问 — 可以订阅、组合和转换数据流
  • ObservedObjectStateObject 自动订阅 @Published 属性 — 不需要手动订阅
  • iOS 17+ @Observable 宏提供了一种替代方案,但 @Published 仍然是 Combine 管道的标准

什么是 @Published?

@Published — 是在 Combine 模块中定义的 property wrapper,它为类的属性添加了自动通知订阅者变更的能力。它只能应用于类内部(而非结构体),并且只能应用于遵宪 ObservableObject 协议的类的属性。

当 @Published 属性的值变化时,Combine 通过内置的 publisher 生成事件,可通过美元前缀访问:$propertyName。这个 publisher 是 ObservableObjectPublisher,属于 ObservableObject 本身。当 view 使用 @ObservedObject 或 @StateObject 时,SwiftUI 会自动订阅它,并在对象内任何 @Published 属性变更时重新绘制 view。

据 Matt Neuburg 的《IOS 18 Programming Fundamentals with Swift》(2025)一书,@Published 是 willSet 模式的一个便捷封装,它自动调用 objectWillChange.send()。编译器实际上将 @Published 展开为带有 willSet 观察器的计算属性,与手动实现相比,运行时开销为零。

对于所有变更应反映在界面中的 ObservableObject 属性,请使用 @Published。对于不影响 UI 的属性,普通的 stored properties 不加 @Published 可以减少不必要的重新绘制。

@Published 如何工作

@Published 在编译时生成两个关键元素。第一个 — 带有 willSet 观察器的存储属性,在写入新值之前调用 objectWillChange.send()。第二个 — 投影 $propertyName,返回一个 Published.Publisher,可以直接在 Combine 管道中使用。

考虑一个带有三个属性的 Settings 类:两个 @Published 和一个普通属性:

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

usernameisDarkMode 变化时,SwiftUI 会重新绘制所有订阅 Settings 实例的 view。lastLogin 的变化不会触发重新绘制。如果需要手动通知订阅者普通属性的变更,可以在 willSet 观察器中调用 objectWillChange.send()

一个重要细节:@Published 仅在直接写入属性时发布变更。如果属性是引用类型(类)且其内部状态在未替换引用的情况下发生变化,@Published 将无法检测到。在这种情况下,需要手动发送事件或替换为值类型(结构体)。

@Published 和 Combine

@Published 与 Combine 紧密集成 — 每个 @Published 属性自动提供一个通过投影 $propertyName 访问的 publisher。这使得可以应用 Combine 运算符进行过滤、转换、组合和延迟处理值。

一个典型场景 — 带有 debounce 的搜索。输入字段绑定到 @Published 属性 searchText,但只能在 300 毫秒停顿后才发送服务器请求。Combine 使用 $searchText.debounce 一行解决此问题:

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

据 John Sundell 的文章(Swift by Sundell, 2024),将 @Published 与 Combine 组合是 SwiftUI 应用程序中响应式管道的标准模式:validation、debounce、throttle、combineLatest、与其他 publisher 合并。@Published 作为强制性 UI 代码和响应式 Combine 之间的桥梁。

@Published 与 @Observable 宏

随着 iOS 17 的发布,Apple 引入了 @Observable 宏,它提供了一种无需 ObservableObject 和 @Published 的替代响应性方法。@Observable 自动在读取层面(而非写入层面)跟踪属性的访问,这提供了更精确的重新绘制 — 只更新读取特定变更属性的 view。

但这并不意味着 @Published 已过时。当需要与 Combine 管道集成时,@Published 仍然必不可少 — 投影 $propertyName 提供了 @Observable 所不具备的 publisher。此外,对于与 iOS 16 及更早版本的向后兼容,@Published+ObservableObject 是唯一选择。据 Apple WWDC 2023 会议《Discover Observation in SwiftUI》所述,Apple 建议新项目使用 @Observable,但明确保留对 @Published 的支持以用于现有代码和 Combine 场景。

在实践中,许多项目采用混合方法:新数据模型使用 @Observable 编写,而现有的 ObservableObject 使用 @Published 保留不变。当需要对发布进行精确控制时,@Published 也是不可替代的 — 例如,将通知延迟到多个属性批量更新完成。

@Published 的常见错误

第一个错误 — 在结构体中应用 @Published。编译器将显示错误:“Property wrapper cannot be applied to a computed property” 或 “'@Published' is only available on members of a class”。@Published 需要引用语义,因为 ObservableObjectPublisher 是一个类,对于每个实例必须是唯一的。

第二个错误 — 在未替换引用的情况下修改引用属性的内容。如果 @Published 属性有数组类型 [String],并调用 array.append("new"),@Published 将无法检测到变更,因为对数组的引用未变。解决方案:为属性赋予新值 array = array + ["new"] 或手动使用 objectWillChange.send()

第三个错误 — @Published 属性过多。每个 @Published 属性都会触发订阅 ObservableObject 的所有 view 重新绘制,而不仅是读取该属性的 view。据 Point-Free (2025),将一个大的 ObservableObject 分解为多个小的,使用 @StateObject 和 @EnvironmentObject,可以减少不必要的重新绘制并提升性能。

代码示例

第一个示例 — 带有验证的注册表单的 ViewModel。@Published 属性 emailpassword 通过 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)
    }
}

第二个示例 — 针对引用元素集合的 手动发布。每次元素内部变更时不替换整个数组,而是使用 objectWillChange.send()

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()  // 手动通知
    }
}

第三个示例 — 通过 Combine 对 @Published 属性进行 Assign。使用 Swift 5.9 的新语法,可以通过投影 assign(to: &$property) 直接赋值,无需 Optional 封装。这是将 publisher 连接到 @Published 属性的最短路径,无需创建订阅。

常见问题

可以在结构体中使用 @Published 吗?

不可以,@Published 只能应用于遵宮 ObservableObject 的类内部。在结构体中,请使用 @State 处理本地状态,或在 iOS 17+ 中使用 @Bindable 与 @Observable 宏。尝试在结构体中应用 @Published 将导致编译错误。

@Published 如何与数组和字典工作?

正确方法:整体赋予新值(array = array + ["new"])。@Published 跟踪引用的替换,而不是内容的修改。对于引用类型的集合,在修改元素内部状态后,请手动调用 objectWillChange.send()

@Published 与 @State 有什么区别?

@State 用于单个 view 内的本地状态,仅支持值类型。@Published 用于 ObservableObject 属性,可以通过 @ObservedObject 或 @EnvironmentObject 被多个 view 读取。@State 更简单,@Published 因与 Combine 集成而更强大。

每个 ObservableObject 属性都需要 @Published 吗?

仅限于那些 变更需要更新 UI 的属性。内部计算、缓存或临时标志不需要 @Published — 这可减少不必要的重新绘制。将 @Published 作为信号使用:“这个属性对界面重要”。

@Published 如何与 Core Data 工作?

SwiftUI 通过 @FetchRequest 和针对 NSManagedObject 的 @ObservedObject 与 Core Data 集成。ManagedObject 已经遵宪 ObservableObject,因此不需要 @Published — NSManagedObject 自行通知变更。@Published 在 Core Data 和 UI 之间的 ViewModel 层中用于数据转换。

总结

  • @Published — 来自 Combine 的 property wrapper,自动发布 ObservableObject 属性变更以供 SwiftUI 和 Combine 管道使用
  • 机制:willSet 观察器调用 objectWillChange.send(),通过投影 $property 生成 publisher
  • Combine:@Published 为 debounce、map、combineLatest 及其他运算符提供 publisher — 这是 UI 与响应式管道之间的桥梁
  • @Observable (iOS 17+) — 新项目的替代方案,但 @Published 仍然是 Combine 和向后兼容的标准
  • 错误:@Published 在结构体中不可用,无法跟踪引用类型的修改,过多 @Published 会增加重新绘制
  • 最佳实践:仅用 @Published 标记影响 UI 的属性,将大的 ObservableObject 拆分为多个小的
  • Assign:Swift 5.9 中的 assign(to: &$property) 可以将 publisher 直接订阅到 @Published 属性

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读