¿UISplitViewController tiene un error de ciclo de retención en iOS 9?

En el siguiente ejemplo, estoy presentando un UIViewController que tiene un UIStackViewController como su hijo:

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

En iOS 9 y 9.1, el código anterior imprimirá the split view controller has leaked , lo que indica que el UIStackViewController se ha filtrado (lo que es más importante, también pierde sus controlleres de vista principal y de detalle).

Sí, el personal de Apple confirmó que existe un error de ciclo de retención en iOS 9.

He probado que el ciclo de retención no existe en iOS 8.4, pero sí existe en iOS 9.0 y 9.1. La fuga parece corregirse a partir del iOS 9.2 (probado en Xcode 7.2 beta 2 en el simulador iOS 9.2) . He armado un proyecto de ejemplo para confirmar fácilmente si UISplitViewController se pierde (simplemente ejecútelo y compruebe la console salida).

Esto también testing un bash de permitir que los controlleres de vista principal y de detalle se desasignen. Como se puede ver, el controller de vista maestra aún parece ser retenido por el UISplitViewController incluso después de que se elimina de la propiedad de matriz UISplitViewController.viewControllers .

Aquí está el código del proyecto de ejemplo:

 - (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.prefernetworkingDisplayMode = UISplitViewControllerDisplayModeAllVisible; splitViewController.prefernetworkingPrimaryColumnWidthFraction = 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.prefernetworkingDisplayMode = UISplitViewControllerDisplayModeAllVisible; splitViewController.prefernetworkingPrimaryColumnWidthFraction = 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"); } }); }); }); } 

ACTUALIZACIÓN: la fuga parece corregirse a partir del iOS 9.2 (probado en Xcode 7.2 beta 2 en el simulador iOS 9.2)

Como sé -[UIViewController addChildViewController:] tiene un problema de pérdida de memory en iOS 9.0~9.1 . Entonces, creo que no es culpa de UISplitViewController. Snipets de la siguiente manera,

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

Encontrará que el dealloc de MyFirstViewController NO se ha llamado si retrocede desde el controller de vista actual.

Una solución posible es usar la vista de contenedor de storyboard en lugar de addChildViewController en código. Estoy seguro de que el controller de vista secundaria de Container View se lanzará correctamente.

Otra solución es removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated . Sin embargo, como mencionó el personal de Apple, esta solución no se recomienda.

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