SwiftUI中计算属性与带参函数的性能差异及缓存问题
一、普通计算属性确实不会缓存
你的判断是对的:这类普通计算属性没有任何缓存机制。
在SwiftUI里,只要视图的@State变量发生变化,整个视图的渲染流程就会重新执行——所有当前渲染用到的计算属性,不管它们依赖的数据源有没有改动,都会被重新求值。比如你修改c的值时,computation1会被调用,而它内部引用了aTimesB,哪怕a和b压根没变化,aTimesB也会被重新计算一次,纯属于不必要的性能浪费。要是实际项目里这类计算数量多、开销大,很容易拖慢主线程。
二、普通带参数函数也不会自动缓存
改成带参数的函数后,默认还是不会缓存结果。
Swift的普通函数没有内置缓存逻辑,不管你传入的参数和上次是不是完全一致,每次调用都会从头执行一遍代码。你给出的函数示例里,哪怕a和b没变化,每次调用aTimesB(a:,b:)都会重新计算a*b,根本不会复用之前的结果,所以性能上不会有任何提升。
三、真正有效的缓存优化方法
要实现“参数不变就复用结果”的效果,得手动添加缓存逻辑,常用的有这几种:
1. 使用@Memoized(Swift 5.9及以上版本)
Swift 5.9新增的@Memoized属性包装器,能直接给函数添加缓存能力,自动根据输入参数存储计算结果:
@State var a: Double @State var b: Double @State var c: Double @Memoized func aTimesB(a: Double, b: Double) -> Double { return a * b } @Memoized func bTimesC(b: Double, c: Double) -> Double { return b * c } @Memoized func computation1(item1: Double, item2: Double) -> Double { return doSuperExpensiveThingWith(item1 + item2) }
只要传入的参数和上次一致,函数就会直接返回缓存的结果,无需重复计算,是最简单高效的方案。
2. 手动实现缓存逻辑(兼容旧版本Swift)
如果项目还没升级到Swift 5.9,可以自己用字典存储参数与结果的映射,建议把缓存放到@StateObject中管理,避免视图重绘时缓存丢失:
@State var a: Double @State var b: Double @State var c: Double @StateObject private var calculator = Calculator() private class Calculator: ObservableObject { // 用字典存储每个函数的缓存,键用参数拼接成字符串 private var aTimesBCache: [String: Double] = [:] private var bTimesCCache: [String: Double] = [:] private var computation1Cache: [String: Double] = [:] func aTimesB(a: Double, b: Double) -> Double { let key = "\(a)-\(b)" // 先查缓存,存在则直接返回 if let cached = aTimesBCache[key] { return cached } // 无缓存则计算并存储 let result = a * b aTimesBCache[key] = result return result } // 同理实现bTimesC和computation1的缓存逻辑 func bTimesC(b: Double, c: Double) -> Double { let key = "\(b)-\(c)" if let cached = bTimesCCache[key] { return cached } let result = b * c bTimesCCache[key] = result return result } func computation1(item1: Double, item2: Double) -> Double { let key = "\(item1)-\(item2)" if let cached = computation1Cache[key] { return cached } let result = doSuperExpensiveThingWith(item1 + item2) computation1Cache[key] = result return result } } // 使用时调用calculator的方法即可 // let finalResult = calculator.computation1(item1: calculator.aTimesB(a: a, b: b), item2: calculator.bTimesC(b: b, c: c))
3. 将计算逻辑移至ViewModel中缓存
如果计算依赖的是@State变量,也可以把计算属性放到ObservableObject类型的ViewModel里,自己追踪依赖变化来实现缓存:
class ViewModel: ObservableObject { @Published var a: Double = 0 @Published var b: Double = 0 @Published var c: Double = 0 // 缓存a*b的结果,以及上次的a、b值 private var cachedATimesB: Double? private var lastA: Double? private var lastB: Double? var aTimesB: Double { // 只有a或b发生变化时,才重新计算 if let cached = cachedATimesB, lastA == a, lastB == b { return cached } let result = a * b cachedATimesB = result lastA = a lastB = b return result } // 用同样逻辑实现bTimesC和computation1 }
这样只有当依赖的变量真的变化时,才会重新计算,否则直接返回缓存值。
内容的提问来源于stack exchange,提问作者Nerdy Bunz

