简体   繁体   English

UISplitViewController在iOS 9中是否存在保留周期错误?

[英]Does UISplitViewController have a retain cycle bug in iOS 9?

In the following example, I am presenting a UIViewController that has a UIStackViewController as its child: 在下面的示例中,我将展示一个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");
            }
        });
    }];
});

In iOS 9 and 9.1, the above code will print the split view controller has leaked , indicating that the UIStackViewController has leaked (more importantly, it leaks its master and detail view controllers as well). 在iOS 9和9.1中,上面的代码将打印the split view controller has leaked ,表明UIStackViewController已泄漏(更重要的是,它也泄漏了其主视图和详细视图控制器)。

Yes, a retain cycle bug has been confirmed to exist in iOS 9 by Apple Staff. 是的,Apple员工已确认 iOS 9中存在保留周期错误。

I've tested that the retain cycle does not exist in iOS 8.4, but does exist in iOS 9.0 and 9.1. 我已经测试过iOS 8.4中不存在保留周期,但在iOS 9.0和9.1中确实存在。 The leak seems to be fixed as of iOS 9.2 (tested in Xcode 7.2 beta 2 on the iOS 9.2 Simulator) I've put together a sample project to easily confirm whether or not UISplitViewController causes itself to leak (just run it and check the console output). 从iOS 9.2(在iOS 9.2模拟器上的Xcode 7.2 beta 2中测试)看,漏洞似乎已得到修复。我已经整理了一个示例项目,以便轻松确认UISplitViewController是否导致自身泄漏(只需运行它并检查控制台)输出)。

This also tests an attempt to allow the master and detail view controllers to be deallocated. 这还测试了允许取消分配主视图控制器和详细视图控制器的尝试。 As one can see, the master view controller still seems to be retained by the UISplitViewController even after it is removed from the UISplitViewController.viewControllers array property. 可以看出,即使从UISplitViewController.viewControllers数组属性中删除了主视图控制器, UISplitViewController似乎仍然保留了它。

Here is the code from the sample project: 以下是示例项目中的代码:

- (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");
                }
            });
        });
    });
}

UPDATE: The leak seems to be fixed as of iOS 9.2 (tested in Xcode 7.2 beta 2 on the iOS 9.2 Simulator) 更新:从iOS 9.2开始,泄漏似乎已得到修复(在iOS 9.2模拟器上的Xcode 7.2 beta 2中测试过)

As I know -[UIViewController addChildViewController:] has memory leak problem in iOS 9.0~9.1 . 据我所知-[UIViewController addChildViewController:]iOS 9.0~9.1有内存泄漏问题。 So I think it not UISplitViewController's fault only. 所以我认为它不仅仅是UISplitViewController的错误。 Snipets as follows, Snipets如下,

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

You will find that MyFirstViewController's dealloc has NOT be called if you retreat from the current view controller. 如果从当前视图控制器撤退,你会发现没有调用MyFirstViewController的dealloc。

A possible workaround is use storyboard's Container View instead of addChildViewController in code. 一种可能的解决方法是在代码中使用storyboard的Container View而不是addChildViewController I'm comfirmed that Container View's child view controller will be released properly. 我确认Container View的子视图控制器将被正确释放。

Another workaround is removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated . 另一种解决方法是removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated However, as apple's staff mentioned , this workaround is not recommended. 但是,正如苹果公司的工作人员所说 ,不推荐这种解决方法。

- (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];
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM