【问题标题】:Does UISplitViewController have a retain cycle bug in iOS 9?UISplitViewController 在 iOS 9 中是否存在保留循环错误?
【发布时间】:2016-01-21 03:34:28
【问题描述】:

在以下示例中,我展示了一个 UIViewController,它的子级为 UIStackViewController

UIViewController *splitViewParentVC = UIViewController.new;

UIViewController *masterVC = UIViewController.new;
UIViewController *detailVC = UIViewController.new;

UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
splitViewController.viewControllers = @[masterVC, detailVC];

[splitViewParentVC addChildViewController:splitViewController];
[splitViewParentVC.view addSubview:splitViewController.view];
[splitViewController didMoveToParentViewController:splitViewParentVC];
splitViewController.view.frame = splitViewParentVC.view.bounds;
splitViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

__weak UISplitViewController *wSplitViewController = splitViewController;

[self presentViewController:splitViewParentVC animated:YES completion:nil];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self dismissViewControllerAnimated:YES completion:^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (wSplitViewController) {
                NSLog(@"the split view controller has leaked");
            } else {
                NSLog(@"the split view controller didn't leak");
            }
        });
    }];
});

在 iOS 9 和 9.1 中,上面的代码会打印出the split view controller has leaked,表示 UIStackViewController 已经泄露(更重要的是,它的主视图控制器和细节视图控制器也泄露了)。

【问题讨论】:

  • 在关闭 splitviewcontroller 之前移除子项是否可以修复泄漏?

标签: ios objective-c memory-leaks ios9 ios9.1


【解决方案1】:

是的,Apple Staff 将 confirmed 保留在 iOS 9 中。

我已经测试过,保留循环在 iOS 8.4 中不存在,但在 iOS 9.0 和 9.1 中存在。 泄漏似乎在 iOS 9.2 中得到修复(在 iOS 9.2 模拟器上的 Xcode 7.2 beta 2 中测试) 我已经整理了一个 sample project 以轻松确认 UISplitViewController 是否导致自身泄漏(只需运行它并检查控制台输出)。

这也测试了允许释放主视图控制器和详细视图控制器的尝试。可以看到,主视图控制器似乎仍然由UISplitViewController 保留,即使它从UISplitViewController.viewControllers 数组属性中删除。

这是示例项目中的代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self testSplitViewControllerRetainCycleWithCompletion:^{
        [self testManuallyFreeingUpMasterAndDetailViewControllers];
    }];
}

- (void)testSplitViewControllerRetainCycleWithCompletion:(void (^)())completion {

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        UIViewController *splitViewParentVC = UIViewController.new;

        UIViewController *masterVC = UIViewController.new;
        UIViewController *detailVC = UIViewController.new;

        UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
        splitViewController.viewControllers = @[masterVC, detailVC];
        splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
        splitViewController.preferredPrimaryColumnWidthFraction = 0.3125; // 320 / 1024
        splitViewController.minimumPrimaryColumnWidth = 100;

        [splitViewParentVC addChildViewController:splitViewController];
        [splitViewParentVC.view addSubview:splitViewController.view];
        [splitViewController didMoveToParentViewController:splitViewParentVC];
        splitViewController.view.frame = splitViewParentVC.view.bounds;
        splitViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

        __weak UISplitViewController *wSplitViewController = splitViewController;
        __weak UIViewController *wMaster = masterVC;
        __weak UIViewController *wDetail = detailVC;

        [self presentViewController:splitViewParentVC animated:YES completion:nil];

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self dismissViewControllerAnimated:YES completion:^{
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    if (wSplitViewController) {
                        NSLog(@"the split view controller has leaked");
                    } else {
                        NSLog(@"the split view controller didn't leak");
                    }
                    if (wMaster) {
                        NSLog(@"the master view controller has leaked");
                    } else {
                        NSLog(@"the master view controller didn't leak");
                    }
                    if (wDetail) {
                        NSLog(@"the detail view controller has leaked");
                    } else {
                        NSLog(@"the detail view controller didn't leak");
                    }

                    completion();
                });
            }];
        });
    });
}

- (void)testManuallyFreeingUpMasterAndDetailViewControllers {

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        UIViewController *splitViewParentVC = UIViewController.new;

        UIViewController *masterVC = UIViewController.new;
        UIViewController *detailVC = UIViewController.new;

        UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
        splitViewController.viewControllers = @[masterVC, detailVC];
        splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
        splitViewController.preferredPrimaryColumnWidthFraction = 0.3125; // 320 / 1024
        splitViewController.minimumPrimaryColumnWidth = 100;

        [splitViewParentVC addChildViewController:splitViewController];
        [splitViewParentVC.view addSubview:splitViewController.view];
        [splitViewController didMoveToParentViewController:splitViewParentVC];
        splitViewController.view.frame = splitViewParentVC.view.bounds;
        splitViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

        __weak UIViewController *wMaster = masterVC;
        __weak UIViewController *wDetail = detailVC;

        [self presentViewController:splitViewParentVC animated:YES completion:nil];

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self dismissViewControllerAnimated:YES completion:nil];

            splitViewController.viewControllers = @[UIViewController.new, UIViewController.new];

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                if (wMaster) {
                    NSLog(@"the master view controller has STILL leaked even after an attempt to free it");
                } else {
                    NSLog(@"the master view controller didn't leak");
                }
                if (wDetail) {
                    NSLog(@"the detail view controller has STILL leaked even after an attempt to free it");
                } else {
                    NSLog(@"the detail view controller didn't leak");
                }
            });
        });
    });
}

更新:从 iOS 9.2 开始,泄漏似乎已得到修复(在 iOS 9.2 模拟器上的 Xcode 7.2 beta 2 中测试)

【讨论】:

  • 有趣的是,当今天 9.1 发布时,您是如何在 iOS 9.4 上对其进行测试的……错字? :P
【解决方案2】:

据我所知-[UIViewController addChildViewController:]iOS 9.0~9.1 中存在内存泄漏问题。所以我认为这不仅仅是 UISplitViewController 的错。片段如下,

- (void)viewDidLoad {
    [super viewDidLoad];
    MyFirstViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"MyFirstViewController"];
    [self addChildViewController:vc];
    [self.view addSubview:vc.view];
    [vc didMoveToParentViewController:self];
}

如果你退出当前的视图控制器,你会发现MyFirstViewController的dealloc并没有被调用。

一种可能的解决方法是在代码中使用故事板的 Container View 而不是 addChildViewController。我确认Container View的子视图控制器会正常释放。

另一种解决方法是removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated。但是,作为苹果员工mentioned,不推荐这种解决方法。

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    NSArray<__kindof UIViewController *> * children = self.childViewControllers;
    for (UIViewController *vc in children) { // not recommended
        [vc willMoveToParentViewController:nil];
        [vc.view removeFromSuperview];
        [vc removeFromParentViewController];
    }
}

【讨论】:

    猜你喜欢
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    • 2018-11-03
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多