Search code examples
iosxcodememory-leaksinstrumentsuipresentationcontroller

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


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.

enter image description here

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.

enter image description here

That is the possible offending call.

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

Solution

  • 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.