UISplitViewController имеет ошибку сохранения цикла в iOS 9?

В следующем примере я представляю 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 (что более важно, он также пропускает свои контроллеры основного и подробного представления).

2 ответа

Да, сотрудники Apple подтвердили, что в iOS 9 существует ошибка сохранения цикла.

Я тестировал, что цикл сохранения не существует в iOS 8.4, но существует в iOS 9.0 и 9.1. Утечка, по-видимому, исправлена ​​в iOS 9.2 (протестировано в Xcode 7.2 beta 2 на симуляторе iOS 9.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 (проверено в Xcode 7.2 beta 2 на Симуляторе iOS 9.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];
}

Вы обнаружите, что dealloc MyFirstViewController НЕ вызывается, если вы отступаете от текущего контроллера представления.

Возможный обходной путь - использование Контейнерного представления раскадровки вместо addChildViewController в коде. Я подтвержден, что дочерний контроллер Контейнерного Представления будет выпущен должным образом.

Другой обходной путь removeChildViewController: в -(void)viewDidDisappear:(BOOL)animated, Однако, как отметили сотрудники Apple, этот обходной путь не рекомендуется.

- (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];
    }
}
Другие вопросы по тегам