为什么 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
}
}
我们需要对它进行更全面的引用,以便手势识别器方法可以与之对话。所以我们在实例 属性 中存储一个引用。但它不是我们要保留的,所以我们不保留。
在 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
}
}
我们需要对它进行更全面的引用,以便手势识别器方法可以与之对话。所以我们在实例 属性 中存储一个引用。但它不是我们要保留的,所以我们不保留。