在使用转换时替换 UIWindow 的 rootViewController,似乎正在泄漏

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

环境
iOS9.2
Xcode7.2

我希望用动画替换 UIWindow's rootViewController,同时也将其从视图层次结构中删除。

class FooViewController: UIViewController
{
}

class LeakedViewController: UIViewController
{
}

然后通过

在 AppDelegate 中启动转换
    self.window!.rootViewController = LeakedViewController()

    let fooViewController = FooViewController()

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

在 Instruments 中对此进行分析,注意 rootViewController 仍在内存中。

也遇到了这个 bug report 这似乎表明 iOS 8.3 中存在同样的问题并且仍然开放。

无法找到任何参考资料来表明作为

的一部分
UIViewController.presentViewController(animated:completion:) 

source view controller is retained (most likely by the UIPresentationController?) 或者这是一个错误。请注意 UIPresentationController 是在 iOS 8.

中首次引入的

如果这是设计使然,是否有发布源代码视图控制器的选项?

使用 UIPresentationController 的子类

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

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

似乎没有任何区别。无法在 SDK 中找到任何其他内容。

目前我找到的唯一方法是使用 UIViewController,在进行转换之前使用屏幕上当前内容的快照代替根视图控制器。

    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
    }

它确实有效,但在控制台中出现以下警告

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

对原始问题或警告消息的任何想法表示赞赏。

更新

我相信我已经将范围缩小到这个缺少版本的保留。

这可能是个冒犯性的电话。

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

我记录了那个错误报告;我没有收到 Apple 工程师的回复。

我随错误报告一起提交的演示该问题的示例代码位于 https://github.com/adurdin/radr21404408

据我所知,这个问题在 iOS 的当前版本中仍然存在,但我没有进行详尽的测试。谁知道,也许 9.3 beta 修复了它? :)

在我遇到这个错误的应用程序中,我们一直在使用自定义转换和 rootViewController 替换大多数屏幕转换。我还没有找到解决此泄漏的方法,并且由于某些原因无法轻松删除所有 rootViewController 操作,因此通过最小化我们使用 presentViewController 和朋友的地方并仔细管理我们需要它的地方来解决这个问题。

我认为有可能避免该错误同时仍保留与 rootViewController 交换类似功能(但尚未实施)的一种方法是让 rootViewController 成为占据全屏的自定义容器视图控制器,并定义了一个表示上下文。我不会交换 window 的 rootViewController,而是交换此容器中的单个 child 视图控制器。而且因为容器定义了表示上下文,所以表示将从容器而不是 child 被交换。这样应该可以避免泄漏。

受@Jordan Smiths 评论的启发,我的修复以一个衬垫结束(感谢 Swift 的美丽):

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

我用动画交换 rootViewController 的完整代码如下所示:

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)
        }
    }

这样我的内存泄漏就消失了:-)

问题可能是呈现的控制器和呈现的视图控制器相互引用。

我只能通过实例化转换到视图控制器的两个副本来让它工作。一种用于在当前根上呈现,另一种用于在呈现后替换当前根。这些副本对我来说很容易实现,因为呈现的 VC 是简单的对象。呈现的视图在关闭后留在 window 层次结构中,因此必须在交换新的 VC.

后手动删除它

这是一些 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()
        }
    }
}