【问题标题】:UIViewPropertyAnimator crashes on dealloc after calling finishAnimation(at:)UIViewPropertyAnimator 在调用 finishAnimation(at:) 后在 dealloc 上崩溃
【发布时间】:2019-12-06 17:00:09
【问题描述】:

当使用 UIViewPropertyAnimator 并在 stopAnimation(false) 之后调用 finishAnimation(at:) 时:

您会得到以下崩溃日志:

- [UIViewPropertyAnimator dealloc]

或者,如果您在调试器中捕获断言(通过为 Objective-C 异常设置断点)

断言失败 -[UIViewPropertyAnimator finishAnimationAtPosition:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3900.12.16/UIViewPropertyAnimator.m:1981

由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“finishAnimationAtPosition:只应在停止的动画师上调用!”

如果您设法捕捉到断言(除非您在发布运行时也捕捉到这些断言,否则崩溃日志并没有那么有用),您会看到它抱怨 finishAnimation 在不间断的动画制作器上被调用。

但我的代码显示动画制作器总是在调用 finishAnimation 之前停止。

animator.stopAnimation(false)
animator.finishAnimation(at: .start)

什么可能导致这个崩溃,在调用stopAnimation之后应该总是停止状态?

【问题讨论】:

  • 在我看来这是UIViewPropertyAnimator 的实现中的一个错误。文档明确指出“如果为 withoutFinishing 参数指定 false,则可以随后调用 finishAnimation(at:) 方法来执行动画制作者的最终操作”

标签: ios uikit


【解决方案1】:

发生这种情况的原因是完成块的时间问题:

表示UIViewPropertyAnimator.stateUIViewAnimatingState)处于无效状态。由于这只是一个 Objective C 枚举,它可以具有超出案例中定义的任何原始值。在我崩溃的情况下,值为5

我正在做一堆相互引用的链接动画,并且有这样的东西:

animator.addCompletion({ _ in
    // Here I called a piece of code that eventually referenced this animator
    // and attempted to call `stopAnimation(false)` and `finishAnimation(at:)` 
    // on it causing the crash.
    //
    // You will notice if you examine the `state` of the animator here, it will have 
    // an invalid `rawValue` of 5, at least up to iOS 13.2, as this is an implementation detail.
})

解决方案是在完成块执行完成之前不要尝试对动画师进行任何操作。

如果你无法避免它,一个适用于当前 UIKit 实现的解决方法是在完成块之外 DispatchQueue.main.async 将允许它完成。当然,这并不能保证状态在未来版本的 iOS 中在该异步内部有效。

在 Swift 上,要调试它,您可以使用以下扩展:

extension UIViewAnimatingState: CustomStringConvertible, CustomDebugStringConvertible {
    var isValid: Bool {
        switch self {
        case .inactive, .active, .stopped:
            return true
        default:
            return false
        }
    }

    public var description: String {
        switch self {
        case .inactive:
            return "inactive"
        case .active:
            return "active"
        case .stopped:
            return "stopped"
        default:
            return "\(rawValue)"
        }
    }

    public var debugDescription: String {
        return description
    }
}

因此,当您致电 stopAnimation(false)finishAnimation(at:) 时,您至少可以避免崩溃并提醒自己动画师可能存在的逻辑问题:

// This is for debugging, the code below should be safe even if isValid is false
assert(animator.state.isValid, "animator state is not valid")

animator.stopAnimation(false)

if animator.state == .stopped {
    // This call is the one that can assert and crash the app
    animator.finishAnimation(at: .start)
}

【讨论】:

  • 很高兴您解决了它,但这个答案绝不是您在问题中所说的任何内容。目前的问题是不充分且无法解决的。
  • 我已经更新了这个问题,以更准确地反映为什么这个崩溃一开始很难调试和令人费解。因为我在我真正有一个正确的问题之前找到了解决方案,所以它就是这样形成的。对于那些将来面临这个问题的人,我认为这篇文章会有所帮助,如果您更新您的投票,我将不胜感激。谢谢。
猜你喜欢
  • 2011-11-17
  • 1970-01-01
  • 1970-01-01
  • 2012-05-27
  • 1970-01-01
  • 1970-01-01
  • 2012-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多