【问题标题】:Subview UINavigationController Leak ARC子视图 UINavigationController 泄漏 ARC
【发布时间】:2015-03-23 17:50:21
【问题描述】:

在子视图中显示和关闭 UINavigationController 时,我遇到了内存泄漏(UINavigationController 及其根视图控制器都被泄漏)。我展示导航控制器的方法似乎有点不标准,所以我希望 SO 社区中的某个人能够提供帮助。

1.演示文稿

导航控制器如下所示:

-(void) presentSubNavigationControllerWithRootViewControllerIdentifier:(NSString *)rootViewControllerIdentifier inStoryboardWithName:(NSString *)storyboardName {

    // grab the root view controller from a storyboard
    UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
    UIViewController * rootViewController = [storyboard instantiateViewControllerWithIdentifier:rootViewControllerIdentifier];

    // instantiate the navigation controller
    UINavigationController * nc = [[UINavigationController alloc] initWithRootViewController:rootViewController];

    // perform some layout configuration that should be inconsequential to memory management (right?)
    [nc setNavigationBarHidden:YES];
    [nc setEdgesForExtendedLayout:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
    nc.view.frame = _navControllerParentView.bounds;

    // install the navigation controller (_navControllerParentView is a persisted IBOutlet)
    [_navControllerParentView addSubview:nc.view];

    // strong reference for easy access
    [self setSubNavigationController:nc];
}

此时,我的期望是导航控制器的唯一“所有者”是父视图控制器(在本例中为self)。但是,当如下所示关闭导航控制器时,它并没有被释放(因此它的rootViewController 也被泄露,依此类推,沿着所有权树向下)。

2。解雇

Dismissal 非常简单,但似乎不足以进行适当的内存管理:

-(void) dismissSubNavigationController {

    // prevent an orphan view from remaining in the view hierarchy
    [_subNavigationController.view removeFromSuperview];

    // release our reference to the navigation controller
    [self setSubNavigationController:nil];
}

肯定有其他东西在“保留”导航控制器,因为它没有被释放。我不认为它可能是根视图控制器保留它,不是吗?

一些研究表明retainCount 毫无意义,但我确定它在解雇后仍保持在 2,我预计它会为零。

是否有完全不同/更好的方法来呈现 subNavigationController?也许在故事板中定义导航控制器比简单地消除对几行代码的需要有更大的好处?

【问题讨论】:

    标签: ios objective-c memory-leaks uinavigationcontroller


    【解决方案1】:

    将控制器的视图添加为另一个控制器视图的子视图时,最佳做法是将添加的视图的控制器设为子视图控制器;也就是说,您将其添加到其视图的控制器应该实现自定义容器控制器 api。一种简单的设置方法是在情节提要中使用容器视图,它会自动为您提供嵌入式控制器(您可以选择该控制器,然后在编辑菜单中选择嵌入导航控制器以获取您尝试的 UI制作)。通常,这个嵌入式视图控制器会在父控制器的视图加载后立即添加,但您可以通过实现 shouldPerformSegueWithIdentifier:sender: 来抑制它。我用这个故事板创建了一个简单的测试应用,

    ViewController 中抑制初始呈现的代码,以及随后呈现和关闭它的按钮方法如下,

    @implementation ViewController
    
    -(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
        if ([identifier isEqualToString:@"Embed"]) { // The embed segue in IB was given this identifier. This method is not called when calling performSegueWithIdentifier:sender: in code (as in the button method below)
            return NO;
        }else{
            return  YES;
        }
    }
    
    
    - (IBAction)showEmbed:(UIButton *)sender {
    
        [self performSegueWithIdentifier:@"Embed" sender:self];
    }
    
    
    - (IBAction)dismissEmbed:(UIButton *)sender {
        [[self.childViewControllers.firstObject view] removeFromSuperview];
        [self.childViewControllers.firstObject willMoveToParentViewController:nil];
        [self.childViewControllers.firstObject removeFromParentViewController];
    }
    
    @end
    

    当按下 Dismiss 按钮时,导航控制器及其任何子视图控制器都会被正确地释放。

    【讨论】:

    • 谢谢,我希望我的“跳舞”子 VC 模式可能是这个问题出现的原因。不久将尝试重构。
    【解决方案2】:

    UIViewController 上的 navigationController 属性是 retain/strong,这可能是另一个强引用。

    所以尝试从导航控制器中弹出所有视图控制器,看看是否可行。

    【讨论】:

    • 这个我真的试过了,navigationController好像不会一直弹到零!
    • @dave BTW 我建议您使用视图控制器包含而不是简单地将其添加为子视图。不确定这是否对您有帮助。
    • 是的,也许采用不同的方法可以解决这个问题。我希望在问题本身的背景下找到解决方案,但我想有时需要重写是现实。我得到的印象是导航控制器通常不会被释放,考虑到它们的轻量级,我认为这没问题。
    • 我能够通过“双缓冲”根视图控制器来实现我想要的目标——例如解散时替换它,以便在下一次演示时有一个新的/空的根 VC。