简体   繁体   English

为什么不在 animation 块中使用弱模式?

[英]Why not using weak pattern in animation blocks?

I know that there are already several questions asked about weak in the context of animations like Is it necessary to use unowned self in closures of UIView.animateWithDuration(…)?我知道在动画的上下文中已经有几个关于weak的问题,例如是否有必要在 UIView.animateWithDuration(...) 的闭包中使用无主的自我? While it is obvious in the latter case, that you can omit weak , I have still difficulties, to see the reason, why I should not use the weak pattern in Robs answer about a rotating view.虽然在后一种情况下很明显,您可以省略weak ,但我仍然很难看到原因,为什么我不应该在Rob 关于旋转视图的回答中使用weak模式。 I do not want to disturb the comments there, so I ask the question here.我不想打扰那里的评论,所以我在这里提出问题。

The code in question is有问题的代码是

private func createAnimation() {
     animator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 4, delay: 0, options: .curveLinear) { [self] in
         UIView.animateKeyframes(withDuration: 4, delay: 0) {
             UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                 animatedView.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
             }
             ...
         }
     } completion: { [weak self] _ in
         self?.createAnimation()
     }
 }

Rob used the [weak self] in the completion -closure but not in the animations -closure, where he actually put self into the capture list to make his intention obvious. Rob 在completion - 闭包中使用了[weak self] ,但在animations - 闭包中没有使用,他实际上将self放入捕获列表以表明他的意图。 How can I know, that UIViewPropertyAnimator.runningPropertyAnimator will never put the (escaping) animations -closure into the created animator -instance-variable?我怎么知道, UIViewPropertyAnimator.runningPropertyAnimator永远不会将(转义) animations -closure 放入创建的animator -instance-variable 中?

I don't think, that UIViewPropertyAnimator.runningPropertyAnimator actually captures the animations -closure, but as long as I have no idea, how UIViewPropertyAnimator.runningPropertyAnimator is implemented, or will be implemented in the future, how can I be sure?我不认为, UIViewPropertyAnimator.runningPropertyAnimator实际上捕获了animations -closure,但只要我不知道, UIViewPropertyAnimator.runningPropertyAnimator是如何实现的,或者将来会实现,我怎么能确定呢?

Maybe this pseudo-implementation could help to explain, what I mean:也许这个伪实现可以帮助解释我的意思:

import Foundation

class UIView {
    var transform = CGFloat.zero
    static func animateKeyFrames(animations: () -> Void) {}
    static func addKeyframe(animations: () -> Void) {}
}

class UIViewPropertyAnimator {
    
    var animations: () -> Void = {}
    var completion: (() -> Void)? = {}
    
    static func runningPropertyAnimator(animations: @escaping () -> Void,
                                        completion: (() -> Void)?) -> UIViewPropertyAnimator {
        let animator = UIViewPropertyAnimator()
        animator.animations = animations
        animator.completion = completion
        
        return animator
    }
}

class ViewController {
    var animator: UIViewPropertyAnimator?
    let animatedView = UIView()
    
    func viewDidLoad() {
        createAnimation()
    }
    
    func createAnimation() {
        animator = UIViewPropertyAnimator.runningPropertyAnimator(animations: { [weak self] in
            UIView.animateKeyFrames(animations: {
                UIView.addKeyframe(animations: {
                    self?.animatedView.transform = .zero
                })
            })
        }, completion: { [weak self] in
            self?.animatedView.transform = .zero
        })
    }
    
    deinit {
        print("deinit")
    }
}

func createAndRelease() {
    let viewController = ViewController()
    viewController.viewDidLoad()
}

createAndRelease()

Removing [weak self] from the animations or completion -closure would obviously cause a retain-cycle in my pseudo-code and deinit would never be called.animationscompletion关闭中删除[weak self]显然会在我的伪代码中导致保留循环,并且永远不会调用deinit

Closure-based animation API have two closures, the animation closure (which is called when the animation starts and is then released) and, optionally, the completion handler closure (which is called when the animation is done). Closure-based animation API have two closures, the animation closure (which is called when the animation starts and is then released) and, optionally, the completion handler closure (which is called when the animation is done).

Consider:考虑:

class ViewController: UIViewController {

    var animator: UIViewPropertyAnimator?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let subview = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        subview.backgroundColor = .red
        view.addSubview(subview)

        logger.debug("creating animator")
        animator = .runningPropertyAnimator(withDuration: 1, delay: 1) {
            logger.debug("animate closure called")
            subview.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
        } completion: { position in
            logger.debug("completion closure called")
        }
    }
}

That will output:那将是 output:

2021-03-31 16:08:00.384558-0700 MyApp[3837:8759577] [ViewController] creating animator
2021-03-31 16:08:00.384899-0700 MyApp[3837:8759577] [ViewController] animate closure called
2021-03-31 16:08:02.386253-0700 MyApp[3837:8759577] [ViewController] completion closure called

Note the timestamps in the above debugging log: The animations closure is called immediately after the animator is created, and well before the completion closure is called.请注意上述调试日志中的时间戳: animations闭包在创建动画后立即调用,并且在调用completion闭包之前。

In your pseudocode example, you seem to be assuming that the animator stores and retains a reference to the animations closure.在您的伪代码示例中,您似乎假设动画师存储并保留对animations闭包的引用。 It does not do that (and nor should it).它没有这样做(也不应该这样做)。 It runs the animation closure to figure out what is being animated and then releases it.它运行animation闭包来确定正在动画的内容,然后将其释放。 It does not keep a strong reference to that closure after the animation begins.在 animation 开始后,它不会强烈引用该关闭。

A better pseudocode example might be:一个更好的伪代码示例可能是:

class UIViewPropertyAnimatorMockup {
    var animations: (() -> Void)?
    var completion: (() -> Void)?

    static func runningPropertyAnimator(animations: @escaping () -> Void, completion: (() -> Void)? = nil) -> UIViewPropertyAnimatorMockup {
        let animator = UIViewPropertyAnimatorMockup()
        animator.animations = animations
        animator.completion = completion
        animator.run()
        return animator
    }

    func run() {
        beginTransaction()
        animations?()
        endTransaction()

        animations = nil         // release that `animations` reference now that we're done with it
    }

    func didFinishAnimations() {
        completion?()
        completion = nil         // release that `completion` reference now that we're done with it
    }
}

This obviously is not precisely what UIViewPropertyAnimator is doing, but the idea to understand is that as soon as it calls the closure and is done with it, it releases its strong reference.这显然不是UIViewPropertyAnimator正在做的事情,但要理解的想法是,一旦它调用闭包并完成它,它就会释放它的强引用。 This applies to both the animations closure and the completion closure (the latter obviously being released a bit later).这适用于animations闭包和completion闭包(后者显然稍后发布)。


As an aside, this is a good pattern to follow in one's own closure-based code.顺便说一句,这是在自己的基于闭包的代码中遵循的一个很好的模式。 If you save a closure in some property, make sure to (a) make it optional;如果您在某些属性中保存了一个闭包,请确保 (a) 将其设为可选; and (b) nil that property as soon as you are done with the closure. (b) 在您完成关闭后立即nil该属性。

This is a defensive programming pattern, mitigating damage if an application developer accidentally introduces a strong reference that they should not have.这是一种防御性编程模式,如果应用程序开发人员不小心引入了他们不应该拥有的强引用,可以减轻损失。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM