SwiftUI中给@Published属性赋值是否需要先校验与原值不同?
是否需要校验赋值
如果你的视图结构简单、该@Published属性赋值频率较低,冗余刷新的开销极小可以忽略;如果视图包含复杂布局、大量子视图,或是该属性存在高频赋值场景,建议做优化避免不必要的body调用。
差异原因
- @State 是SwiftUI内置的状态包装器,内部默认实现了等值判断逻辑,仅当存储的值实际发生变更时才会触发视图重绘。
- @Published 属于Combine框架的属性包装器,默认逻辑为:只要触发属性赋值操作,无论新旧值是否相等,都会立即发送所属
ObservableObject的objectWillChange信号,进而通知所有观察该对象的视图刷新,这就是你示例中每次点击都触发body调用的核心原因。
优化方案
方案1:赋值前手动校验值变更
兼容性最好、实现最简单的方案,仅在值实际变化时执行赋值操作即可:
class Object: ObservableObject { @Published var state: Bool = false func setState() { guard state != false else { return } state = false } }
如果有大量同类型属性需要优化,可以自定义带等值判断的@Published属性包装器,统一避免冗余通知:
@propertyWrapper struct PublishedNoDuplicate<Value: Equatable> { private var value: Value private let publisher = PassthroughSubject<Value, Never>() var wrappedValue: Value { get { value } set { guard newValue != value else { return } value = newValue publisher.send(newValue) } } var projectedValue: AnyPublisher<Value, Never> { publisher.eraseToAnyPublisher() } init(wrappedValue: Value) { self.value = wrappedValue } }
方案2:视图层用EquatableView做拦截
如果不想修改ObservableObject的代码,可以在视图层做等值判断,SwiftUI会先校验视图是否真的需要重绘:
extension ContentView: Equatable { static func == (lhs: ContentView, rhs: ContentView) -> Bool { lhs.state == rhs.state && lhs.obj.state == rhs.obj.state } }
使用视图时外层套一层EquatableView即可:
EquatableView(ContentView())
方案3:iOS 17+ 直接使用@Observable宏
iOS 17推出的Observation框架从底层解决了这个问题,它实现了属性级别的精准变更检测,仅当视图实际读取的属性发生真实变更时才会触发刷新,相同值赋值不会产生任何冗余调用:
@Observable class Object { var state: Bool = false func setState() { state = false } } struct ContentView: View { @State var state: Bool = false var obj = Object() // 无需再使用@ObservedObject包装 var body: some View { print(Self._printChanges()) return VStack { Text("\(String(state))") Button("Tap") { self.state = false obj.setState() } } } }
上述代码运行后连续点击按钮不会产生任何刷新日志。
内容的提问来源于stack exchange,提问作者Valera
相关产品推荐
相关产品推荐

