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)
jonsibley Oct 21 '15 at 23:44 2015-10-21 23:44
source share