SwiftUI使用ObservableObject时如何避免不必要重渲染?性能问题与解决
问题解答
1. 会不会引发性能问题?
会。哪怕当前视图只有简单的Text,频繁(比如每0.1秒一次)的无意义重渲染也会持续占用CPU资源,长期下来会导致设备耗电增加;如果视图结构复杂(比如包含大量子视图、动画、或者需要计算的内容),还会直接引发界面卡顿,影响用户体验。
2. 兼容iOS17以下的解决办法
方法一:赋值前手动判断值是否变化
最简单的方式就是在给test赋值前,先判断当前值和新值是否不同,只有不同时才执行赋值:
func loadTimer(){ timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in guard self.test != false else { return } self.test = false } }
这种方式适合单个属性的简单场景,直接避免无意义的@Published通知触发。
方法二:自定义仅在值变化时触发通知的属性包装器
如果项目里有多个类似场景,可以封装一个自定义的属性包装器,利用Equatable协议自动判断值是否变化,只有真正变化时才发送更新通知:
import SwiftUI import Combine @propertyWrapper struct EquatablePublished<Value: Equatable>: Publisher { typealias Output = Value typealias Failure = Never @Published private var value: Value var wrappedValue: Value { get { value } set { guard value != newValue else { return } value = newValue } } var projectedValue: Published<Value>.Publisher { $value } init(wrappedValue initialValue: Value) { self._value = Published(wrappedValue: initialValue) } func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Value == S.Input { $value.receive(subscriber: subscriber) } }
然后把ViewModel里的@Published替换成这个自定义包装器:
class ViewModel:ObservableObject{ @EquatablePublished var test = false var timer:Timer! init(){ } func loadTimer(){ timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in self.test = false } } }
这样不管你怎么重复赋值相同值,都不会触发视图更新通知。
方法三:拆分视图减少重渲染范围
如果无法从ViewModel层面解决,可以把视图中不依赖test属性的部分拆成独立子视图。SwiftUI会自动优化子视图的渲染,只有当子视图依赖的状态变化时才会重渲染。比如:
struct StaticContentView: View { var body: some View { Text("Hello, world!") } } struct ContentView: View { @StateObject var viewModel = ViewModel() var body: some View { let _ = Self._printChanges() VStack { StaticContentView() } .onAppear{ viewModel.loadTimer() } .padding() } }
不过这个方法是“治标不治本”,最好还是从源头阻止无意义的状态更新通知。
内容的提问来源于stack exchange,提问作者Perry Wang
相关产品推荐
相关产品推荐

