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

ObservableObject多@Published属性引发不必要重绘的解决方法

解决ObservableObject引发的SwiftUI不必要视图更新问题

当使用ObservableObject中的@Published属性时,任意属性的变更都会触发持有该对象的父视图及其所有子视图的body重新计算,导致不必要的性能损耗,在iOS 14中甚至可能引发卡顿、冻结问题。以下是几种针对性的解决方案:


方案1:给子视图实现Equatable并启用相等性检查

SwiftUI默认会在父视图更新时重新渲染所有子视图,除非子视图实现Equatable协议,通过属性对比决定是否需要更新。

  1. 让子视图遵循Equatable协议,仅对比关键依赖属性:
struct SomeSimpleComponent: View, Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        // 只有绑定的property值变化时,视图才需要更新
        lhs.property == rhs.property
    }

    @Binding var property: Int

    var body: some View {
        print("component 1")
        HStack {
            Text("\(property)")
            Button("Increment") { property += 1 }
        }
    }
}

struct SomeSimpleComponent2: View, Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.property == rhs.property
    }

    @Binding var property: Int

    var body: some View {
        print("component 2")
        HStack {
            Text("\(property)")
            Button("Increment") { property += 1 }
        }
    }
}
  1. 在父视图中调用子视图时添加.equatable()修饰符:
struct ObjectBindingTest: View {
    @StateObject private var model = Model()

    var body: some View {
        print("——— top")
        VStack(spacing: 30) {
            SomeSimpleComponent(property: $model.property1).equatable()
            SomeSimpleComponent2(property: $model.property2).equatable()
        }
        .padding(50)
    }
}

此时点击第一个按钮,控制台只会输出——— top和component 1,component 2不会触发body计算。


方案2:拆分ObservableObject为多个独立状态对象

将原有的大Model拆分为多个专注单个属性的小ObservableObject,让每个子视图仅观察自己依赖的状态对象,避免单个属性变更触发全局更新。

// 拆分后的独立状态对象
class Property1Model: ObservableObject {
    @Published var value: Int = 0
}

class Property2Model: ObservableObject {
    @Published var value: Int = 0
}

struct ObjectBindingTest: View {
    @StateObject private var model1 = Property1Model()
    @StateObject private var model2 = Property2Model()

    var body: some View {
        print("——— top")
        VStack(spacing: 30) {
            SomeSimpleComponent(model: model1)
            SomeSimpleComponent2(model: model2)
        }
        .padding(50)
    }
}

// 子视图仅观察自身需要的状态对象
struct SomeSimpleComponent: View {
    @ObservedObject var model: Property1Model

    var body: some View {
        print("component 1")
        HStack {
            Text("\(model.value)")
            Button("Increment") { model.value += 1 }
        }
    }
}

struct SomeSimpleComponent2: View {
    @ObservedObject var model: Property2Model

    var body: some View {
        print("component 2")
        HStack {
            Text("\(model.value)")
            Button("Increment") { model.value += 1 }
        }
    }
}

更新model1的属性只会触发SomeSimpleComponent的更新,component 2因依赖的model2未变化,不会重新计算body。


方案3:使用Combine手动订阅特定属性(适用于只读/自定义更新逻辑)

如果子视图无需修改属性,或需要自定义更新时机,可以直接订阅目标属性的Publisher,避免观察整个ObservableObject。

class Model: ObservableObject {
    @Published var property1: Int = 0
    @Published var property2: Int = 0

    // 暴露单个属性的Publisher
    var property1Publisher: AnyPublisher<Int, Never> {
        $property1.eraseToAnyPublisher()
    }

    var property2Publisher: AnyPublisher<Int, Never> {
        $property2.eraseToAnyPublisher()
    }
}

struct ObjectBindingTest: View {
    @StateObject private var model = Model()

    var body: some View {
        print("——— top")
        VStack(spacing: 30) {
            SomeSimpleComponent(
                publisher: model.property1Publisher,
                initialValue: model.property1,
                onIncrement: { model.property1 += 1 }
            )
            SomeSimpleComponent2(
                publisher: model.property2Publisher,
                initialValue: model.property2,
                onIncrement: { model.property2 += 1 }
            )
        }
        .padding(50)
    }
}

struct SomeSimpleComponent: View {
    @State private var localValue: Int
    let publisher: AnyPublisher<Int, Never>
    let onIncrement: () -> Void

    init(publisher: AnyPublisher<Int, Never>, initialValue: Int, onIncrement: @escaping () -> Void) {
        self.publisher = publisher
        self._localValue = State(initialValue: initialValue)
        self.onIncrement = onIncrement
    }

    var body: some View {
        print("component 1")
        HStack {
            Text("\(localValue)")
            Button("Increment", action: onIncrement)
        }
        .onReceive(publisher) { newValue in
            localValue = newValue
        }
    }
}

// SomeSimpleComponent2实现逻辑与上述一致,此处省略

子视图仅在订阅的属性变更时更新,父视图更新不会影响子视图的body计算。


方案4:iOS 17+ 使用@Observable宏(推荐)

如果项目已升级到iOS 17或以上,苹果推出的@Observable宏可自动实现细粒度状态观察,无需手动拆分对象或实现Equatable:

import Observation

@Observable
class Model {
    var property1: Int = 0
    var property2: Int = 0
}

struct ObjectBindingTest: View {
    @State private var model = Model()

    var body: some View {
        print("——— top")
        VStack(spacing: 30) {
            SomeSimpleComponent(property: $model.property1)
            SomeSimpleComponent2(property: $model.property2)
        }
        .padding(50)
    }
}

// 子视图无需额外修改
struct SomeSimpleComponent: View {
    @Binding var property: Int

    var body: some View {
        print("component 1")
        HStack {
            Text("\(property)")
            Button("Increment") { property += 1 }
        }
    }
}

@Observable会自动为每个属性生成独立订阅信号,只有依赖对应属性的视图才会更新,无需额外配置。


内容的提问来源于stack exchange,提问作者Morpheus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 09:10:28