为什么 Debug Memory Graph 不显示 UIViewControllerContextTransitioning 对 _animator (UIViewControllerInteractiveTransitioning) 的强引用?

Why Debug Memory Graph do not show UIViewControllerContextTransitioning strong reference to _animator (UIViewControllerInteractiveTransitioning)?

在 Matt Neuburg 的书中,我正在阅读有关交互式自定义过渡动画的内容,在他写的示例中,我偶然发现了这一行:

 weak var context : UIViewControllerContextTransitioning?

我在想为什么这个 属性 被标记为弱。 动画师的完整代码:

 class Animator : NSObject {
var anim : UIViewImplicitlyAnimating?
unowned var tbc : UITabBarController
weak var context : UIViewControllerContextTransitioning?
var interacting = false
init(tabBarController tbc: UITabBarController) {
    self.tbc = tbc
    super.init()
    let sep = UIScreenEdgePanGestureRecognizer(target:self, action:#selector(pan))
    sep.edges = UIRectEdge.right
    tbc.view.addGestureRecognizer(sep)
    sep.delegate = self
    let sep2 = UIScreenEdgePanGestureRecognizer(target:self, action:#selector(pan))
    sep2.edges = UIRectEdge.left
    tbc.view.addGestureRecognizer(sep2)
    sep2.delegate = self
    

      }
 }

 extension Animator: UITabBarControllerDelegate {

func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    print("animation controller")
    return self
}

func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    print("interaction controller")
    return self.interacting ? self : nil
   }
}

extension Animator : UIGestureRecognizerDelegate {

func gestureRecognizerShouldBegin(_ g: UIGestureRecognizer) -> Bool {
    let ix = self.tbc.selectedIndex
    let a =  (g as! UIScreenEdgePanGestureRecognizer).edges == .right ?
        ix < self.tbc.viewControllers!.count - 1 : ix > 0
    return a
      
}

@objc func pan(_ g:UIScreenEdgePanGestureRecognizer) {
    
    switch g.state {
    case .began:
        self.interacting = true
        if g.edges == .right {
            self.tbc.selectedIndex = self.tbc.selectedIndex + 1
        } else {
            self.tbc.selectedIndex = self.tbc.selectedIndex - 1
        }
    case .changed:
        let v = g.view!
        let delta = g.translation(in:v)
        let percent = abs(delta.x/v.bounds.size.width)
        self.anim?.fractionComplete = percent
        self.context?.updateInteractiveTransition(percent)
    case .ended:
        // this is the money shot!
        // with a property animator, the whole notion of "hurry home" is easy -
        // including "hurry back to start"
        
        let anim = self.anim as! UIViewPropertyAnimator
        anim.pauseAnimation()

        if anim.fractionComplete < 0.5 {
            anim.isReversed = false
        }
        anim.continueAnimation(
            withTimingParameters:
            UICubicTimingParameters(animationCurve:.linear),
            durationFactor: 0.2)

    case .cancelled:
        print("cancelled")
        self.anim?.pauseAnimation()
        self.anim?.stopAnimation(false)
        self.anim?.finishAnimation(at: .start)
        
    default: break
    }
  }
}

extension Animator : UIViewControllerInteractiveTransitioning {

// called if we are interactive
// (because we now have no percent driver)
func startInteractiveTransition(_ ctx: UIViewControllerContextTransitioning){
    print("startInteractiveTransition")
    
    // store the animator so the gesture recognizer can get at it
   _ = self.interruptibleAnimator(using: ctx)
    
    // store transition context so the gesture recognizer can get at it
    self.context = ctx
    
    // I don't like having to store them both
    // I could make this look neater with a "helper object"
    // but really, they ought to give me nicer way
    
}
}

 extension Animator : UIViewControllerAnimatedTransitioning {

func interruptibleAnimator(using ctx: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    print("interruptibleAnimator")
    
    if self.anim != nil {
        return self.anim!
    }
    
    let vc1 = ctx.viewController(forKey:.from)!
    let vc2 = ctx.viewController(forKey:.to)!
    
    let con = ctx.containerView
    
    let r1start = ctx.initialFrame(for:vc1)
    let r2end = ctx.finalFrame(for:vc2)
    
    // new in iOS 8, use these instead of assuming that the views are the views of the vcs
    let v1 = ctx.view(forKey:.from)!
    let v2 = ctx.view(forKey:.to)!
    
    // which way we are going depends on which vc is which
    // the most general way to express this is in terms of index number
    let ix1 = self.tbc.viewControllers!.firstIndex(of:vc1)!
    let ix2 = self.tbc.viewControllers!.firstIndex(of:vc2)!
    let dir : CGFloat = ix1 < ix2 ? 1 : -1
    var r1end = r1start
    r1end.origin.x -= r1end.size.width * dir
    var r2start = r2end
    r2start.origin.x += r2start.size.width * dir
    v2.frame = r2start
    con.addSubview(v2)
    
    let anim = UIViewPropertyAnimator(duration: 0.4, curve: .linear) {
        v1.frame = r1end
        v2.frame = r2end
    }
    anim.addCompletion { finish in
        if finish == .end {
            ctx.finishInteractiveTransition()
            ctx.completeTransition(true)
        } else {
            ctx.cancelInteractiveTransition()
            ctx.completeTransition(false)
        }
    }
    
    self.anim = anim
    print("creating animator")
    return anim
}

func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
    print("transitionDuration")
    return 0.4
}

// called if we are not interactive
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
    print("animateTransition")
    
    let anim = self.interruptibleAnimator(using: ctx)
    anim.startAnimation()
    
}

func animationEnded(_ transitionCompleted: Bool) {
    print("animation ended")
    // reset everything
    self.interacting = false
    self.anim = nil
    delay(1) {
        // prove the context goes out of existence in good order
        print(self.context as Any)
        }
    }
}

乍一看,UIViewControllerContextTransitioning 中没有对 Animator 的明显引用(对我而言),因此我尝试查看 Variables View。

确实上下文是指动画师。然后我试图在 Debug Memory Graph 的帮助下找到这个事实,但它没有向我显示(或者我未能找到对动画师的引用)。

如何在调试内存图中找到它,请帮助我。

context的引用很弱,仅仅是因为它属于运行时。它作为 UIViewControllerInteractiveTransitioning 对象交给我们:

extension Animator : UIViewControllerInteractiveTransitioning {
    func startInteractiveTransition(_ ctx: UIViewControllerContextTransitioning) {
        // ...
        self.context = ctx
    }
}

我们需要对它进行更全面的引用,以便手势识别器方法可以与之对话。所以我们在实例 属性 中存储一个引用。但它不是我们要保留的,所以我们不保留。