Replacing the UIWindow's rootViewController while using a transition, appears to be leaking

人走茶凉 提交于 2019-12-10 04:28:45

问题


Environment
iOS 9.2
Xcode 7.2

I'm looking to replace the UIWindow's rootViewController with an animation while also removing it from the view hierarchy as well.

class FooViewController: UIViewController
{
}

class LeakedViewController: UIViewController
{
}

Then initiate the transition in the AppDelegate simply by

    self.window!.rootViewController = LeakedViewController()

    let fooViewController = FooViewController()

    self.window!.rootViewController?.presentViewController(fooViewController, animated: true){ unowned let window = self.window!
        window.rootViewController = fooViewController
    }

Profiling this in Instruments, notice that the rootViewController is still in memory.

Also came across this bug report which seems to suggest the same issue is present in iOS 8.3 and still Open.

Haven't been able to find any references to suggest that as part of the

UIViewController.presentViewController(animated:completion:) 

the source view controller is retained (most likely by the UIPresentationController?) or if this is a bug. Notice that the UIPresentationController was first introduced in iOS 8.

If that's by design, is there an option to release the source view controller?

Using a subclass of UIPresentationController with

override func shouldPresentInFullscreen() -> Bool {
    return true
}

override func shouldRemovePresentersView() -> Bool {
    return true
}

doesn't seem make any difference. Haven't been able to locate anything else in the SDK.

Currently the only way I have found is to use a UIViewController, with a snapshot of what's currently on screen, in place of the root view controller before making the transition.

    let fooViewController = FooViewController()

    let view = self.window!.snapshotViewAfterScreenUpdates(false)
    let viewController = UIViewController()
    viewController.view.addSubview(view)

    self.window!.rootViewController = viewController
    self.window!.rootViewController?.presentViewController(dashboardViewController!, animated: true){ unowned let window = self.window!
        window.rootViewController = fooViewController
    }

It does work, tho in the console the following warning appears

Unbalanced calls to begin/end appearance transitions for <UIViewController: 0x79d991f0>.

Any ideas on the original question or the warning message appreciated.

Update

I believe I have narrowed it down to this one retain that's missing a release.

That is the possible offending call.

 0 UIKit -[UIPresentationController _presentWithAnimationController:interactionController:target:didEndSelector:]

回答1:


I logged that bug report; I have had no response from Apple engineering on it.

The sample code I submitted with the bug report demonstrating the issue is at https://github.com/adurdin/radr21404408

As far as I am aware, the issue is still present in current versions of iOS, but I have not tested exhaustively. Who knows, perhaps 9.3 beta fixes it? :)

In the application where I encountered this bug, we had been using custom transitions and rootViewController replacement for the majority of screen transitions. I have not found a solution to this leak, and because of reasons could not easily remove all the rootViewController manipulation, so instead worked around the issue by minimising where we used presentViewController and friends, and carefully managing the places where we required it.

One approach that I think has potential to avoid the bug while still retaining similar capabilities to rootViewController swapping--but have not yet implemented--is to have the rootViewController be a custom container view controller that occupies the full screen, and defines a presentation context. Instead of swapping the window's rootViewController, I would swap the single child view controller in this container. And because the container defines the presentation context, the presentations will occur from the container instead of the child being swapped. This should then avoid the leaks.




回答2:


Inspired by @Jordan Smiths comment my fix ended with a one liner (thanks to the beauty of Swift):

window.rootViewController?.dismissViewControllerAnimated(false, completion: nil)

My complete code to swap the rootViewController with an animation then looks like:

func swapRootViewController(newController: UIViewController) {
        if let window = self.window {
            window.rootViewController?.dismissViewControllerAnimated(false, completion: nil)

            UIView.transitionWithView(window, duration: 0.3, options: .TransitionCrossDissolve, animations: {
                window.rootViewController = newController
            }, completion: nil)
        }
    }

With that my memory leak disappeared :-)




回答3:


The problem is probably that the presented controller and the presenting view controller refer to each other.

I could only get this to work by instantiating two copies of the transitioned-to view controller. One for presenting on the current root and one for replacing the current root after presentation. The copies are easy to achieve for me, since the presented VC's are simple objects. The presented view is left in the window hierarchy after dismissal, so that has to be removed manually after swapping in the new VC.

Here's some Swift.

private func present(_ presented: UIViewController, whenPresentedReplaceBy replaced: @escaping () -> UIViewController)
{
    presented.modalTransitionStyle = .crossDissolve
    let currentRoot = self.window?.rootViewController
    currentRoot?.present(presented, animated: true)
    {
        let nextRoot = replaced()
        self.window?.rootViewController = nextRoot
        currentRoot?.dismiss(animated: false) {
            currentRoot?.view?.removeFromSuperview()
        }
    }
}


来源:https://stackoverflow.com/questions/34813889/replacing-the-uiwindows-rootviewcontroller-while-using-a-transition-appears-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!