在 SwiftUI 中,objectWillChange.send()
是 ObservableObject
协议的核心方法,用于通知 SwiftUI 视图模型中的数据即将发生变化。以下是详细的用法说明和实际场景:
基础机制 #
// ObservableObject 协议定义
public protocol ObservableObject: AnyObject {
var objectWillChange: ObservableObjectPublisher { get }
}
当模型数据变化时,调用 objectWillChange.send()
会触发视图更新:
graph LR
A[Model Property Change] --> B[objectWillChange.send]
B --> C[SwiftUI View Update]
主要使用场景 #
1. 手动触发视图更新 #
当不使用 @Published
属性包装器时手动通知变化:
class UserSettings: ObservableObject {
var fontSize: CGFloat = 14 {
didSet {
// 手动触发视图更新
objectWillChange.send()
}
}
}
2. 批量属性更新优化性能 #
一次改变多个属性时减少视图刷新次数:
class Document: ObservableObject {
@Published var title: String = ""
@Published var content: String = ""
func updateDocument(newTitle: String, newContent: String) {
// 开始更新前通知(对象即将变化)
objectWillChange.send()
title = newTitle
content = newContent
// 这里不需要再次发送,值设置后视图会自动更新
}
}
3. 结合外部框架或系统功能 #
整合 Core Data、Core Location 等系统框架:
import CoreLocation
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var currentLocation: CLLocationCoordinate2D?
override init() {
super.init()
locationManager.delegate = self
}
// CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
// 更新前通知
objectWillChange.send()
currentLocation = location.coordinate
}
}
4. 异步操作前通知 #
在执行异步操作前通知视图:
class ImageLoader: ObservableObject {
@Published var image: UIImage?
func loadImage(from url: URL) {
// 通知视图将要加载图片
objectWillChange.send()
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else { return }
DispatchQueue.main.async {
// 再次通知视图图片即将更新
self.objectWillChange.send()
self.image = UIImage(data: data)
}
}.resume()
}
}
5. 自定义通知逻辑 #
添加额外逻辑控制何时通知视图:
class GameState: ObservableObject {
var score: Int = 0 {
didSet {
// 只有分数变化超过10时才通知
if abs(score - oldValue) >= 10 {
objectWillChange.send()
}
}
}
}
最佳实践 #
与 @Published 的关系
class Model: ObservableObject { // 自动通知 (推荐) @Published var name: String = "" // 手动通知 (特殊场景) var age: Int = 0 { didSet { objectWillChange.send() } } }
主线程要求
DispatchQueue.main.async { self.objectWillChange.send() // 必须在主线程调用 }
使用场景对比
方式 适用场景 特点 @Published
简单属性更新 自动触发通知 objectWillChange.send()
批量更新/异步操作 更精细控制 组合使用 复杂场景 最灵活的方式 注意事项
- 避免在视图初始化时调用
- 不要与视图层的
onReceive
绑定 - 性能敏感应用中慎用,避免过度刷新
完整示例 #
class WeatherViewModel: ObservableObject {
// 自动通知
@Published var temperature: Double = 0
@Published var humidity: Int = 0
// 手动通知
var conditions: String = "Sunny" {
didSet {
// 只在有明显变化时通知
if conditions != oldValue {
objectWillChange.send()
}
}
}
func updateWeather() async {
// 通知视图将要开始更新
objectWillChange.send()
async let temp = fetchTemperature()
async let hum = fetchHumidity()
async let cond = fetchConditions()
// 批量更新前再次通知
objectWillChange.send()
// 应用所有更新
self.temperature = await temp
self.humidity = await hum
self.conditions = await cond
}
}
调试与工具 #
使用自定义发布者监控通知:
class DebuggableObject: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
init() {
// 监控所有通知
objectWillChange
.sink { _ in
print("Change detected at: \(Date())")
print("Call stack: \(Thread.callStackSymbols.joined(separator: "\n"))")
}
.store(in: &cancellables)
}
private var cancellables = Set<AnyCancellable>()
}
总结 #
objectWillChange.send()
在以下场景最有用:
- 避免多次刷新:批量更新属性时
- 非标准属性:不适用
@Published
的属性 - 外部系统集成:Core Data、Core Location等
- 自定义更新逻辑:需要条件通知时
- 异步操作前通知:加载状态指示
在大多数简单场景中,优先使用 @Published
,在复杂场景中使用 objectWillChange.send()
提供更精细的控制。