@Published — 是来自 Combine 框架的 property wrapper,能够自动发布遵宪 ObservableObject 协议的类的属性变更。当标记为 @Published 的属性值发生变化时,SwiftUI 通过 objectWillChange 接收到信号,并重新绘制所有订阅此对象的 view。据 Apple Combine Framework Documentation (2025),@Published 生成一个 Publisher,可以通过 Combine 运算符(map、filter、debounce 等)进一步转换。这使得 @Published 成为 MVVM 架构中数据与用户界面之间的关键桥梁。
核心要点
objectWillChange,从而触发订阅的 view 重新绘制$property 访问 — 可以订阅、组合和转换数据流@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 在编译时生成两个关键元素。第一个 — 带有 willSet 观察器的存储属性,在写入新值之前调用 objectWillChange.send()。第二个 — 投影 $propertyName,返回一个 Published.Publisher,可以直接在 Combine 管道中使用。
考虑一个带有三个属性的 Settings 类:两个 @Published 和一个普通属性:
class Settings: ObservableObject {
@Published var username: String = "Guest"
@Published var isDarkMode = false
var lastLogin: Date = Date() // 无 @Published
}
当 username 或 isDarkMode 变化时,SwiftUI 会重新绘制所有订阅 Settings 实例的 view。lastLogin 的变化不会触发重新绘制。如果需要手动通知订阅者普通属性的变更,可以在 willSet 观察器中调用 objectWillChange.send()。
一个重要细节:@Published 仅在直接写入属性时发布变更。如果属性是引用类型(类)且其内部状态在未替换引用的情况下发生变化,@Published 将无法检测到。在这种情况下,需要手动发送事件或替换为值类型(结构体)。
@Published 与 Combine 紧密集成 — 每个 @Published 属性自动提供一个通过投影 $propertyName 访问的 publisher。这使得可以应用 Combine 运算符进行过滤、转换、组合和延迟处理值。
一个典型场景 — 带有 debounce 的搜索。输入字段绑定到 @Published 属性 searchText,但只能在 300 毫秒停顿后才发送服务器请求。Combine 使用 $searchText.debounce 一行解决此问题:
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 之间的桥梁。
随着 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。编译器将显示错误:“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 属性 email 和 password 通过 Combine 管道触发验证错误的显示:
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():
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 只能应用于遵宮 ObservableObject 的类内部。在结构体中,请使用 @State 处理本地状态,或在 iOS 17+ 中使用 @Bindable 与 @Observable 宏。尝试在结构体中应用 @Published 将导致编译错误。
正确方法:整体赋予新值(array = array + ["new"])。@Published 跟踪引用的替换,而不是内容的修改。对于引用类型的集合,在修改元素内部状态后,请手动调用 objectWillChange.send()。
@State 用于单个 view 内的本地状态,仅支持值类型。@Published 用于 ObservableObject 属性,可以通过 @ObservedObject 或 @EnvironmentObject 被多个 view 读取。@State 更简单,@Published 因与 Combine 集成而更强大。
仅限于那些 变更需要更新 UI 的属性。内部计算、缓存或临时标志不需要 @Published — 这可减少不必要的重新绘制。将 @Published 作为信号使用:“这个属性对界面重要”。
SwiftUI 通过 @FetchRequest 和针对 NSManagedObject 的 @ObservedObject 与 Core Data 集成。ManagedObject 已经遵宪 ObservableObject,因此不需要 @Published — NSManagedObject 自行通知变更。@Published 在 Core Data 和 UI 之间的 ViewModel 层中用于数据转换。
总结
objectWillChange.send(),通过投影 $property 生成 publisherassign(to: &$property) 可以将 publisher 直接订阅到 @Published 属性我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。