Lmsgsendnilself

Uninhibited Soul, Free Craziness

切换根视图内存泄漏问题

| Comments

  最近在测试app性能时,发现了内存泄漏,跟踪发现问题出现在方法setRootViewController,这个方法的内存泄漏位置并不是启动app设置根视图的地方,是我在项目中用来切换根视图的代码:

1
2
3
4
5
  [UIView transitionWithView:self.window
              duration:0.6
               options:UIViewAnimationOptionTransitionFlipFromLeft
            animations:^{ self.window.rootViewController = mathEditorViewController; }
            completion:nil];


  这个地方是用来干啥的呢,简单描述下,当前的根控制器为oldController,oldController上面呈现着一个newFunctionController,而要被切换成的新的根控制器为mathEditorViewController;这个方法正式为了切换新旧根控制器。
  断点跟踪代码,查看相关控制器内存和视图层级后发现一通后,发现在transitionWithView:执行完后,newFunctionsController并没有随着oldController移除而移除。虽然在使用app时看似并没有什么问题,但切换后的视图层级分布却出乎意料。 在transitionWithView:执行完后,使用app时看似并没有什么问题,但是视图层级发生了根本变化。

| UIWindowLayer
  | UITransitionView
    | newFunctionController view
  | mathEditorViewController view

  又做了如下两个测试:
在transitionWithView:转场动画前执行 [oldController dismissViewControllerAnimated:NO]方法或者在transitionWithView:的completion块,但结果是显示层级都为:

| UIWindowLayer
  | oldController view
  | mathEditorViewController view

  看来内存泄漏是由于切换根控制器时旧的控制器没有移除释放干净导致的。

  解决方案:既然采用直接的方式移除不干净,那就利用递归遍历的方式对根控制器及其呈现的控制器进行移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- (void)setRootViewController:(UIViewController )rootViewController {
/ dismiss all presented view controllers before change rootViewController /
        UIViewController presentedViewController = [self traversePresentedViewControllerFrom:self.window.rootViewController];
    [self dismissPresentedViewController:presentedViewController completion:^{
        [self.window setRootViewController:rootViewController];
    }];
}

- (void)dismissPresentedViewController:(UIViewController )vc completion:(void(^)())block {

    if ([vc presentingViewController]) {
        __block UIViewController nextVC = vc.presentingViewController;
        [vc dismissViewControllerAnimated:NO completion:^ {
            // if the view controller which is presenting vc is also presented by other view controller, dismiss it
            if ([nextVC presentingViewController]) {
                [self dismissPresentedViewController:nextVC completion:block];
            } else {
                if (block) {
                    block();
                }
            }
        }];
    } else {
        if (block != nil) {
            block();
        }
    }
}

- (UIViewController )traversePresentedViewControllerFrom:(UIViewController )startViewController {
    if ([startViewController isKindOfClass:[UINavigationController class]]) {
        return [self traversePresentedViewControllerFrom:[(UINavigationController )startViewController topViewController]];
    }

    if ([startViewController isKindOfClass:[UITabBarController class]]) {
        return [self traversePresentedViewControllerFrom:[(UITabBarController )startViewController selectedViewController]];
    }

    if (startViewController.presentedViewController == nil || startViewController.presentedViewController.isBeingDismissed) {
        return startViewController;
    }

    return [self traversePresentedViewControllerFrom:startViewController.presentedViewController];
}

Comments