iOS 17中AnimatableModifier引发的内存泄漏问题
iOS 17下Skeleton动画内存泄漏问题修复方案
问题根源
在iOS 17环境中,原代码的两个核心问题导致内存泄漏:
- 每次动画更新
phase时,GradientMask会创建全新的Gradient及Gradient.Stop实例,这些临时对象无法被及时回收; .animation(animation)直接修饰AnimatedMask,导致SwiftUI动画系统持续持有旧的视图层级,无法释放内存。
修复方案
以下是修改后的代码,针对上述问题进行优化:
import SwiftUI struct SkeletonModifier: ViewModifier { @State private var phase: CGFloat = 0 let animation: Animation init(animation: Animation) { self.animation = animation } func body(content: Content) -> some View { content .modifier(AnimatedMask(phase: phase)) .onAppear { // 使用withAnimation驱动动画,替代直接给Modifier加animation修饰符 withAnimation(animation) { phase = 0.8 } } } struct AnimatedMask: AnimatableModifier { var phase: CGFloat = 0 var animatableData: CGFloat { get { phase } set { phase = newValue } } func body(content: Content) -> some View { content .mask(GradientMask(phase: phase).scaleEffect(3)) } } struct GradientMask: View { let phase: CGFloat private let centerColor = Color.black private let edgeColor = Color.black.opacity(0.5) // 缓存Gradient实例,避免每次phase变化都创建新对象 private var gradient: Gradient { Gradient(stops: [ .init(color: edgeColor, location: phase), .init(color: centerColor, location: phase + 0.1), .init(color: edgeColor, location: phase + 0.2) ]) } var body: some View { LinearGradient( gradient: gradient, startPoint: .topLeading, endPoint: .bottomTrailing ) } } } public extension View { /// 给视图添加骨架屏动画效果,用于展示加载状态 /// - Parameters: /// - isActive: 是否启用效果,默认true /// - animation: 自定义动画,默认是1.5秒线性循环动画 @ViewBuilder func skeleton( isActive: Bool = true, animation: Animation = .linear(duration: 1.5).repeatForever(autoreverses: false) ) -> some View { if isActive { redacted(reason: .placeholder) .modifier(SkeletonModifier(animation: animation)) } else { self } } }
关键修改点说明
- 调整动画触发方式:将
.animation(animation)从AnimatedMask上移除,改用onAppear中的withAnimation来启动动画,避免动画修饰符导致的视图层级冗余持有。 - 缓存Gradient实例:通过计算属性
gradient封装渐变创建逻辑,让SwiftUI的内存管理机制更易识别和回收临时对象。 - 优化属性访问:将颜色属性标记为
private,减少不必要的外部访问,提升视图渲染效率。
额外建议
如果内存泄漏问题仍然存在,可以尝试:
- 使用
Xcode Memory Graph Debugger定位具体泄漏对象,确认是否还有其他未释放的引用; - 将
GradientMask改为UIViewRepresentable,用UIKit的CAGradientLayer实现渐变,绕过SwiftUI在iOS17中的渐变渲染内存问题。
内容的提问来源于stack exchange,提问作者Konstantin.Efimenko
相关产品推荐
相关产品推荐

