7

在以下示例中,我将展示一个UIViewController以 aUIStackViewController作为其子级的 a:

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 已经泄漏(更重要的是,它也泄漏了它的主视图控制器和详细视图控制器)。

4

2 回答 2

4

是的, Apple Staff已确认iOS 9 中存在保留周期错误。

我已经测试过,保留周期在 iOS 8.4 中不存在,但在 iOS 9.0 和 9.1 中确实存在。从 iOS 9.2 开始,泄漏似乎已得到修复(在 iOS 9.2 模拟器上的 Xcode 7.2 beta 2 中测试)我整理了一个示例项目以轻松确认 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 中测试)

于 2015-10-21T23:44:39.293 回答
0

据我所知-[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 并没有被调用。

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

另一种解决方法是removeChildViewController:-(void)viewDidDisappear:(BOOL)animated. 但是,正如苹果的工作人员所提到的,不推荐这种解决方法。

- (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];
    }
}
于 2015-11-13T07:25:35.587 回答