调用objectWillChange.send()致性能问题,SwiftUI嵌套对象更新如何优化?
嵌套对象结构的SwiftUI视图更新问题
我有一个嵌套对象结构的项目:Project -> Panels -> Layers,通过SettingsView更新这些结构的各类属性。每次更新时必须调用objectWillChange.send()才能让视图即时显示变更;对象数量较少时运行正常,但对象增多后出现性能问题。
代码示例
class SelectedProjectHolder: ObservableObject { @Published var projects: [Project] = [] @Published var selectedProject: Project? = nil // ...其他代码 func updateTextLayerColor(for textLayer: TextLayer, newColor: Color) { guard let selectedProject = selectedProject else { return } for (panelIndex, panel) in selectedProject.panels.enumerated() { if let layerIndex = panel.textLayers.firstIndex(where: { $0.id == textLayer.id }) { selectedProject.panels[panelIndex].textLayers[layerIndex].color = newColor objectWillChange.send() return } } } // ...其他代码 } struct MainApp: App { let selectedProjectHolder = SelectedProjectHolder() var body: some Scene { WindowGroup { ContentView(lastSaveTime: lastSaveTime) .environmentObject(selectedProjectHolder) } .commands { SidebarCommands() } } } // Panel视图示例 struct PanelView: View { @EnvironmentObject var selectedProjectHolder: SelectedProjectHolder @Binding var panel: Panel // ...其他代码 }
问题
我忽略了什么?为何不显式调用objectWillChange.send()就无法生效?有没有办法在修改SelectedProjectHolder的selectedProject属性时,无需每次调用该方法就能让视图即时更新?
问题根源
@Published只能检测顶层属性的赋值操作,比如直接给selectedProject赋新对象。但你修改的是selectedProject内部嵌套对象的属性(比如textLayers里的color),这种深层变更不会触发@Published的自动通知,所以必须手动调用objectWillChange.send()。- 手动调用会触发所有订阅
SelectedProjectHolder的视图全局刷新,当对象数量增多时,大量不必要的重绘就会引发性能问题。
解决方案
1. 让嵌套对象也成为ObservableObject
把Project、Panel、TextLayer都改成遵循ObservableObject的类,用@Published标记需要监听的属性:
class Project: ObservableObject { @Published var panels: [Panel] = [] // 其他属性 } class Panel: ObservableObject { @Published var textLayers: [TextLayer] = [] // 其他属性 } class TextLayer: ObservableObject { let id: UUID @Published var color: Color init(id: UUID, color: Color) { self.id = id self.color = color } }
这样修改TextLayer的color时,会自动触发自身的变更通知,只有订阅该层级的视图会精准刷新,避免全局重绘。
2. 视图直接绑定嵌套对象的Published属性
修改PanelView,让它直接观察对应Panel或TextLayer的变化,不再只依赖顶层的SelectedProjectHolder:
struct PanelView: View { @ObservedObject var panel: Panel // 替换为@ObservedObject var body: some View { ForEach($panel.textLayers) { $layer in ColorPicker("图层颜色", selection: $layer.color) // 修改color会自动触发视图刷新 } } }
这种方式只会刷新被修改的具体图层视图,大幅提升性能。
3. 不可变值类型模式(可选)
如果不需要嵌套对象是引用类型,把Project、Panel、TextLayer改成结构体(值类型)。值类型的嵌套属性变更会被视为顶层对象的整体变更,触发@Published的自动通知:
struct Project { var panels: [Panel] = [] } struct Panel { var textLayers: [TextLayer] = [] } struct TextLayer { let id: UUID var color: Color }
此时更新属性需要通过不可变方式生成新对象,赋值给selectedProject触发通知:
func updateTextLayerColor(for textLayer: TextLayer, newColor: Color) { guard var selectedProject = selectedProject else { return } selectedProject.panels = selectedProject.panels.map { panel in var updatedPanel = panel if let index = updatedPanel.textLayers.firstIndex(where: { $0.id == textLayer.id }) { updatedPanel.textLayers[index].color = newColor } return updatedPanel } self.selectedProject = selectedProject // 赋值触发@Published通知 }
这种方式适合结构简单的场景,不可变性还能避免多线程问题,但复杂嵌套下更新代码会相对繁琐。
内容的提问来源于stack exchange,提问作者Alex Filatov
相关产品推荐
相关产品推荐

