问题:销毁视图控制器后CAKeyframeAnimation仍在后台持续运行
解决CAKeyframeAnimation在视图控制器销毁后仍后台运行的问题
这问题我之前做动画时也踩过坑,核心是视图控制器销毁时没有彻底清理动画相关的资源,导致动画实例和代理引用还在后台“赖着”。咱们一步步拆解原因和解决办法:
问题根源
- 你通过
animationDidStop回调重复启动动画,但如果没有正确处理self的弱引用,当视图控制器被销毁后,动画的代理仍指向已释放的VC内存,系统可能还会继续执行动画逻辑。 - 即使你把
isRemovedOnCompletion设为true,但因为动画是重复触发的,只要前一次动画的回调还在触发,新的动画就会被不断添加,哪怕VC已经不存在了。
具体解决方案
1. 在视图控制器销毁时彻底清理动画和Layer
在你的视图控制器中添加deinit方法,手动移除所有相关动画、清空代理,并把Layer从父层移除:
deinit { // 移除Layer上的所有动画 aBall.removeAllAnimations() // 清空动画代理,避免野指针或无效引用 if let anim = aBall.animation(forKey: "ballPositionAnimation") as? CAKeyframeAnimation { anim.delegate = nil } // 将Layer从父层彻底移除 aBall.removeFromSuperlayer() }
注意:给动画设置一个明确的
key(比如add(_:forKey:)里的"ballPositionAnimation"),这样能更精准地找到并清理目标动画。
2. 在动画回调中使用弱引用捕获self
修改animationDidStop方法,用[weak self]避免强引用导致VC无法被释放,同时判断self是否存在后再执行动画重启逻辑:
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { // 动画未完成的话直接返回 guard flag else { return } DispatchQueue.main.async { [weak self] in // 确认self还存在再执行后续操作 guard let self = self else { return } // 重新创建并添加动画的逻辑 let ballFrameAnimation = CAKeyframeAnimation() ballFrameAnimation.keyPath = "position" ballFrameAnimation.duration = 3.0 ballFrameAnimation.calculationMode = kCAAnimationDiscrete ballFrameAnimation.fillMode = kCAFillModeForwards ballFrameAnimation.isRemovedOnCompletion = true ballFrameAnimation.delegate = self self.aBall.add(ballFrameAnimation, forKey: "ballPositionAnimation") } }
这样一来,当VC被销毁后,self会变成nil,回调里的动画重启逻辑就不会再执行了。
3. 额外:在视图消失时提前清理
如果你的VC是被push或present后消失的,可以在viewWillDisappear里提前清理动画,避免不必要的资源消耗:
override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParent || isBeingDismissed { aBall.removeAllAnimations() aBall.removeFromSuperlayer() } }
内容的提问来源于stack exchange,提问作者Ed Hauptman
相关产品推荐
相关产品推荐

