Skip to content

Commit

Permalink
Prevent the potential memory leaks in the modal transition (#429)
Browse files Browse the repository at this point in the history
This dismisses the frame 'FloatingPanel Core.move(from:to:animated:completion:)'
in the following memory leaks

> BoardServices -[BSXPCServiceConnectionEventHandler remoteTarget]
> BoardServices __63+[BSXPCServiceConnectionProxy createImplementationForProtocol:]_block_invoke

These leaks happens when a panel showes and hides using "Show Multi Panel Modal"
in the Samples app.
  • Loading branch information
scenee authored Feb 6, 2021
1 parent 11dfc0d commit 9958fc5
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 10 deletions.
25 changes: 19 additions & 6 deletions Sources/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class Core: NSObject, UIGestureRecognizerDelegate {
let panGestureRecognizer: FloatingPanelPanGestureRecognizer
var isRemovalInteractionEnabled: Bool = false

fileprivate var animator: UIViewPropertyAnimator?
fileprivate var isSuspended: Bool = false // Prevent a memory leak in the modal transition
fileprivate var transitionAnimator: UIViewPropertyAnimator?
fileprivate var moveAnimator: NumericSpringAnimator?

private var initialSurfaceLocation: CGPoint = .zero
Expand Down Expand Up @@ -158,12 +159,15 @@ class Core: NSObject, UIGestureRecognizerDelegate {
animator.addCompletion { [weak self] _ in
guard let self = self else { return }

self.animator = nil
self.transitionAnimator = nil
updateScrollView()
self.ownerVC?.notifyDidMove()
completion?()
}
self.animator = animator
self.transitionAnimator = animator
if isSuspended {
return
}
animator.startAnimation()
} else {
self.state = to
Expand Down Expand Up @@ -376,7 +380,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
if interactionInProgress {
lockScrollView()
} else {
if state == layoutAdapter.edgeMostState, self.animator == nil {
if state == layoutAdapter.edgeMostState, self.transitionAnimator == nil {
switch layoutAdapter.position {
case .top, .left:
if offsetDiff < 0 && velocity > 0 {
Expand Down Expand Up @@ -496,7 +500,7 @@ class Core: NSObject, UIGestureRecognizerDelegate {
animator.stopAnimation(true)
endAttraction(false)
}
if let animator = self.animator {
if let animator = self.transitionAnimator {
guard 0 >= layoutAdapter.offsetFromEdgeMost else { return }
log.debug("a panel animation(interruptible: \(animator.isInterruptible)) interrupted!!!")
if animator.isInterruptible {
Expand Down Expand Up @@ -1038,7 +1042,7 @@ public final class FloatingPanelPanGestureRecognizer: UIPanGestureRecognizer {
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
initialLocation = touches.first?.location(in: view) ?? .zero
if floatingPanel?.animator != nil || floatingPanel?.moveAnimator != nil {
if floatingPanel?.transitionAnimator != nil || floatingPanel?.moveAnimator != nil {
self.state = .began
}
}
Expand Down Expand Up @@ -1199,3 +1203,12 @@ private class NumericSpringAnimator: NSObject {
v = (v + h * o2 * (xt - x)) / det
}
}

extension FloatingPanelController {
func suspendTransitionAnimator(_ suspended: Bool) {
self.floatingPanel.isSuspended = suspended
}
var transitionAnimator: UIViewPropertyAnimator? {
return self.floatingPanel.transitionAnimator
}
}
30 changes: 26 additions & 4 deletions Sources/Transitioning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,25 @@ class ModalPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
return TimeInterval(animator.duration)
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .to) as? FloatingPanelController
else { fatalError() }

fpc.show(animated: true) {
if let animator = fpc.transitionAnimator {
return animator
}

fpc.suspendTransitionAnimator(true)
fpc.show(animated: true) { [weak fpc] in
fpc?.suspendTransitionAnimator(false)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return fpc.transitionAnimator!
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}

Expand All @@ -111,14 +122,25 @@ class ModalDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
return TimeInterval(animator.duration)
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
guard
let fpc = transitionContext.viewController(forKey: .from) as? FloatingPanelController
else { fatalError() }

fpc.hide(animated: true) {
if let animator = fpc.transitionAnimator {
return animator
}

fpc.suspendTransitionAnimator(true)
fpc.hide(animated: true) { [weak fpc] in
fpc?.suspendTransitionAnimator(false)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return fpc.transitionAnimator!
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.interruptibleAnimator(using: transitionContext).startAnimation()
}
}

0 comments on commit 9958fc5

Please sign in to comment.