【问题标题】:Replacing the UIWindow's rootViewController while using a transition, appears to be leaking在使用过渡时替换 UIWindow 的 rootViewController,似乎正在泄漏
【发布时间】:2016-04-21 05:07:22
【问题描述】:

环境
iOS 9.2
Xcode 7.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 被保留(很可能是 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:]

【问题讨论】:

  • 看到完全一样的东西,这让我很头疼……未发布的控制器保留了他们旧的 Core Data 对象/获取的结果控制器并导致崩溃。你有没有想出一个“不错”的解决方案或类似的东西?
  • @Jordan AFAICS,没有单一的解决方案。可能就像我描述的屏幕截图一样简单,丢弃您不需要的视图层次结构(只保留叶子)或根据您的上下文重用/释放内存的方法。
  • 我最终编写了一个文件修复程序 - 如果您有兴趣,我可能会分享代码。它是如何工作的:从 UIViewController 的扩展开始,它劫持 viewDidLoad() 以获取对每个活动视图控制器的引用。单例对象使用 KVO 观察窗口的 rootController 属性,在发生任何更改之前,它会向 rootViewController 层次结构中即将泄漏内存的每个控制器发送dismissViewController(animated: false)。

标签: ios xcode memory-leaks instruments uipresentationcontroller


【解决方案1】:

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

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

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

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

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

【讨论】:

【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    我只能通过实例化转换到视图控制器的两个副本来使其工作。一种用于在当前根上呈现,一种用于在呈现后替换当前根。这些副本对我来说很容易实现,因为呈现的 VC 是简单的对象。显示的视图在关闭后保留在窗口层次结构中,因此必须在切换到新的 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()
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-09-14
      • 1970-01-01
      • 2021-10-01
      • 2013-10-16
      • 2015-01-01
      • 2017-02-18
      • 1970-01-01
      • 2011-12-03
      • 1970-01-01
      相关资源
      最近更新 更多