10

环境
iOS 9.2
Xcode 7.2

我希望用动画替换UIWindow 的 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 仍在内存中。

在此处输入图像描述

还遇到了这个错误报告,这似乎表明 iOS 8.3 中存在相同的问题并且仍然打开。

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

UIViewController.presentViewController(animated:completion:) 

源视图控制器被保留(很可能由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:]
4

3 回答 3

7

我记录了那个错误报告;我没有得到 Apple 工程部门的回应。

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

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

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

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

于 2016-01-15T15:39:01.950 回答
5

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

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

于 2016-12-31T08:45:28.673 回答
1

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

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

这是一些斯威夫特。

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()
        }
    }
}
于 2017-06-09T13:40:32.233 回答