Does UISplitViewController have a save loop error in iOS 9?

In the following example, I present a UIViewController that has a UIStackViewController as its child:

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

On 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 also leaks its main and detailed view controllers).

+5
ios memory-leaks objective-c ios9
Oct 21 '15 at 23:44
source share
2 answers

Yes, a save cycle error has been confirmed to exist in Apple Staff's iOS 9.

I tested that the save loop does not exist in iOS 8.4, but exists in iOS 9.0 and 9.1. It seems that the leak is fixed with iOS 9.2 (tested in Xcode 7.2 beta 2 on the iOS 9.2 simulator) I put together a sample project to easily check if the UISplitViewController is leaking (just run it and check the console output).

It also checks for an attempt to free the master of the basic and detailed views. As you can see, the main view controller is still saved by the UISplitViewController even after it is removed from the UISplitViewController.viewControllers array UISplitViewController.viewControllers .

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 fixed as iOS 9.2 (tested in Xcode 7.2 beta 2 on iOS 9.2 Simulator)

+4
Oct 21 '15 at 23:44
source share

As I know, -[UIViewController addChildViewController:] has a memory leak problem in iOS 9.0~9.1 . Therefore, I think this is not a UISplitViewController error. The snipers are next,

 - (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 dealloc is NOT called if you step back from the current view controller.

A possible workaround is to use the Container View storyboard instead of addChildViewController in the code. I am sure that the controller of the child view of the Container View will be released correctly.

Another workaround is removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated . However, as mentioned in the Apple app , 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]; } } 
0
Nov 13 '15 at 7:25
source share



All Articles