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

如何基于Swift Observation框架按需高效更新ExerciseSearchModel的filteredExercises属性

如何基于Swift Observation框架按需高效更新ExerciseSearchModel的filteredExercises属性

嘿,我完全懂你现在的困惑——刚从Combine或者老的ObservableObject切换到Swift Observation框架时,确实容易在这种“按需更新计算属性”的场景下卡壳。你提到的计算属性频繁重算、Combine不符合未来技术栈的顾虑都非常合理,其实用Observation框架有一套非常优雅且高效的方案,完全贴合Apple的最新设计思路,我这就给你拆解清楚。

核心思路:缓存+依赖追踪+手动变更通知

我们的目标是:只有当exercises、searchQuery、filters、sort中任意一个属性变化时,才重新计算filteredExercises,并且用缓存存储结果,避免重复计算。Observation框架本身提供了精确的属性追踪能力,我们只需要结合缓存和手动触发视图更新就能实现。

具体代码实现

直接修改你的ExerciseSearchModel,核心改动是用缓存属性存储计算结果,通过Observation的追踪机制监听依赖变化,按需更新缓存并通知视图:

import Observation

@Observable
public class ExerciseSearchModel: RouteAware {
    // 原始可观察属性(框架自动追踪变化)
    public var exercises: [Exercise] = []
    public var searchQuery: String = ""
    public var filters: ExerciseFilters = .init()
    public var sort: ExerciseSort = .defaultValue
    
    // 缓存计算结果:用@ObservationIgnored忽略自动观察,我们手动管理更新时机
    @ObservationIgnored
    private var _filteredExercises: IdentifiedArrayOf<Exercise> = []
    
    // 对外暴露的只读属性,直接返回缓存值
    public var filteredExercises: IdentifiedArrayOf<Exercise> {
        get {
            // 初始化兜底:如果缓存为空且有数据,先计算一次初始值
            if _filteredExercises.isEmpty && !exercises.isEmpty {
                updateFilteredExercises()
            }
            return _filteredExercises
        }
    }
    
    // 假设你有FilterExercisesUseCase的引用,这里先声明
    private let filterExercisesUseCase: FilterExercisesUseCase
    
    public init(filterExercisesUseCase: FilterExercisesUseCase) {
        self.filterExercisesUseCase = filterExercisesUseCase
        // 初始化时启动依赖监听
        setupDependencyTracking()
    }
    
    // 设置依赖属性的变化监听
    private func setupDependencyTracking() {
        withObservationTracking {
            // 访问所有需要追踪的依赖属性,告诉框架要监听它们的变化
            _ = self.exercises
            _ = self.searchQuery
            _ = self.filters
            _ = self.sort
        } onChange: { [weak self] in
            // 当任何一个依赖属性变化时,重新计算并更新缓存
            self?.updateFilteredExercises()
            // 手动发送变更通知,让视图知道filteredExercises已更新
            self?.objectWillChange.send()
            // 重新建立监听(withObservationTracking是单次的,触发后需要重置)
            self?.setupDependencyTracking()
        }
    }
    
    // 抽离实际的计算逻辑,代码更清晰
    private func updateFilteredExercises() {
        guard !exercises.isEmpty else {
            _filteredExercises = []
            return
        }
        let result = filterExercisesUseCase.execute(
            exercises: exercises,
            query: searchQuery,
            filters: filters,
            sort: sort
        )
        _filteredExercises = IdentifiedArrayOf(uniqueElements: result)
    }
}

关键细节解释

  1. @ObservationIgnored的作用:
    我们把缓存用的_filteredExercises标记为@ObservationIgnored,是因为我们不想让框架自动追踪这个属性的变化——毕竟它是我们手动更新的缓存,我们要自己控制什么时候通知视图更新,而不是让框架自动触发不必要的刷新。

  2. withObservationTracking的用法:
    这个API是Observation框架的核心之一,它允许我们精确监听指定属性的变化。我们在闭包里访问四个依赖属性,框架就会自动记录这些依赖;当其中任何一个变化时,onChange闭包就会触发。
    注意:withObservationTracking是单次监听,触发后就会失效,所以我们需要在onChange里再次调用setupDependencyTracking()来重新建立监听,这样就能持续追踪后续的变化。

  3. 手动发送objectWillChange:
    因为我们更新的是@ObservationIgnored的缓存属性,框架不会自动通知视图变化,所以需要手动调用objectWillChange.send()——这会告诉所有观察这个模型的视图:“嘿,有属性更新了,赶紧刷新!”,视图就会重新读取filteredExercises的缓存值。

  4. 缓存的初始化处理:
    在filteredExercises的getter里,我们做了一个兜底:如果缓存为空但exercises有数据,就先计算一次。这样第一次访问filteredExercises时,就能得到正确的初始值,不需要等到第一次依赖变化。

为什么这个方案比你之前的尝试更好?

  • 完全贴合Observation框架:没有用Combine,完全基于Apple最新的响应式方案,符合未来的技术栈方向。
  • 按需计算,无冗余:只有当四个依赖属性中的任何一个变化时,才会重新计算filteredExercises,彻底解决了计算属性每次访问都重算的问题。
  • 缓存机制高效:视图每次访问filteredExercises都是直接取缓存,不会触发计算,性能拉满。
  • 代码简洁易维护:所有依赖追踪逻辑都集中在setupDependencyTracking()里,不需要给每个属性加didSet,也不需要写Combine的combineLatest,代码更干净。

额外注意事项

  • 如果ExerciseFilters或ExerciseSort是引用类型(而不是值类型struct),那你需要把它们也标记为@Observable,这样Observation框架才能追踪它们内部属性的变化。不过从你的代码看,它们应该是值类型,所以没问题。
  • 一定要用weak self在onChange闭包里,避免循环引用导致内存泄漏。
  • 如果你的filterExercisesUseCase.execute()是异步方法,那你需要把updateFilteredExercises()改成异步的,并且在异步回调里更新缓存后再发送objectWillChange。不过从你的描述看,这个方法是同步的,所以当前写法没问题。

这个方案完全符合Swift Observation框架的设计哲学,既解决了性能问题,又保持了代码的简洁性,而且是Apple主推的未来方向。你可以直接把这段代码套进去,应该就能完美解决你的问题啦!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 14:03:12