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

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
        }
    }
}

关键修改点说明

  1. 调整动画触发方式:将.animation(animation)从AnimatedMask上移除,改用onAppear中的withAnimation来启动动画,避免动画修饰符导致的视图层级冗余持有。
  2. 缓存Gradient实例:通过计算属性gradient封装渐变创建逻辑,让SwiftUI的内存管理机制更易识别和回收临时对象。
  3. 优化属性访问:将颜色属性标记为private,减少不必要的外部访问,提升视图渲染效率。

额外建议

如果内存泄漏问题仍然存在,可以尝试:

  • 使用Xcode Memory Graph Debugger定位具体泄漏对象,确认是否还有其他未释放的引用;
  • 将GradientMask改为UIViewRepresentable,用UIKit的CAGradientLayer实现渐变,绕过SwiftUI在iOS17中的渐变渲染内存问题。

内容的提问来源于stack exchange,提问作者Konstantin.Efimenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:01:17