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

时间:2016-01-15 14:58:52

标签: ios xcode memory-leaks instruments uipresentationcontroller

环境
iOS 9.2
Xcode 7.2

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

 * {
   margin: 0;
   padding: 0;
 }
 
 #header {
   position: fixed;
   top: 0;
   left: 0;
   min-width: 320px;
   max-width: 100%;
   width: 100%;
   height: 100%;
   background: url("http://placehold.it/960x290") no-repeat top center;
 }
 
 #main {
   position: relative;
   font-family: 'Hind', sans-serif;
   color: black;
   text-align: center;
   font-size: 2.5vw;
   background-color: transparent;
   padding-top: 50vh; /* for test purposes only */
   height: 100vh; /* for test purposes only */
 }

然后只需通过

启动AppDelegate中的转换
<div id="header">
</div>

<div id="main">
  <table>
    <tr>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
    </tr>
    <tr>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
    </tr>
    <tr>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
      <td>info</td>
    </tr>
  </table>
</div>

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

enter image description here

还遇到了这个bug report,这似乎表明iOS 8.3中存在同样的问题,但仍然是开放的。

Haven能够找到任何建议,作为

的一部分
class FooViewController: UIViewController
{
}

class LeakedViewController: UIViewController
{
}

保留source view controller(最有可能是UIPresentationController?)或者这是一个错误。请注意,UIPresentationController最初是在iOS 8中引入的。

如果是设计,是否有选项来释放源视图控制器?

使用UIPresentationController的子类

    self.window!.rootViewController = LeakedViewController()

    let fooViewController = FooViewController()

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

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

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

UIViewController.presentViewController(animated:completion:) 

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

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

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

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

更新

我相信我已经把它缩小到了这个保留错过了一个版本。

enter image description here

这是可能违规的电话。

    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
    }

3 个答案:

答案 0 :(得分:4)

我记录了该错误报告;我对Apple工程没有回应。

我提交的示例代码以及显示问题的错误报告位于https://github.com/adurdin/radr21404408

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

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

我认为有一种方法可以避免这个错误,同时仍然保留与rootViewController交换相似的功能 - 但尚未实现 - 是让rootViewController成为占用整个屏幕的自定义容器视图控制器,并定义演示文稿上下文。我不会交换窗口的rootViewController,而是在这个容器中交换单个子视图控制器。并且因为容器定义了表示上下文,所以演示将从容器而不是交换子进行。这应该可以避免泄漏。

答案 1 :(得分:4)

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

随着我的记忆泄漏消失了: - )

答案 2 :(得分:0)

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

我只能通过实例化转换到视图控制器的两个副本来实现此功能。一个用于呈现当前根,一个用于在呈现后替换当前根。这些副本很容易实现,因为所呈现的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()
        }
    }
}