You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:15:24