2015-10-21 15 views
5

Nell'esempio seguente, che vi presento un UIViewController che ha un UIStackViewController come il suo bambino:UISplitViewController ha un bug del ciclo di mantenimento in iOS 9?

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 e 9.1, il codice sopra stamperà the split view controller has leaked, indicando che l'UIStackViewController è fuoriuscito (cosa ancora più importante, perde anche i suoi controller di vista principale e di dettaglio).

+0

La rimozione del bambino prima di chiudere lo splitviewcontroller risolve la perdita? – yuf

risposta

4

Sì, un bug del ciclo di mantenimento è stato confirmed esistente in iOS 9 dallo staff Apple.

Ho verificato che il ciclo di conservazione non esiste in iOS 8.4, ma esiste in iOS 9.0 e 9.1. La perdita sembra essere corretta a partire da iOS 9.2 (testata in Xcode 7.2 beta 2 su iOS 9.2 Simulator) Ho messo insieme uno sample project per confermare facilmente se UISplitViewController si fa perdere (basta eseguirlo e controllare il uscita console).

Questo verifica anche un tentativo di consentire la deallocazione dei controller di vista principale e di dettaglio. Come si può vedere, il controller della vista master sembra ancora essere mantenuto dal UISplitViewController anche dopo che è stato rimosso dalla proprietà dell'array UISplitViewController.viewControllers.

Ecco il codice dal progetto di esempio:

- (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: La perdita sembra essere fissato come di iOS 9.2 (testato in Xcode 7.2 beta 2 sul iOS 9.2 Simulator)

+0

Interessante come l'hai testato su iOS 9.4 quando 9.1 è uscito oggi ... Typo? : P – erdekhayser

0

Come noto, lo -[UIViewController addChildViewController:] presenta un problema di perdita di memoria in iOS 9.0~9.1. Quindi penso che non sia colpa solo di UISplitViewController. Snipets come segue,

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

Troverete che dealloc di MyFirstViewController ha non può essere chiamato se si rifugio dal controller della vista corrente.

Una possibile soluzione è utilizzare lo storyboard Visualizzazione contenitore anziché addChildViewController nel codice. Sono convinto che il controller di visualizzazione figlio di Container View verrà rilasciato correttamente.

Un'altra soluzione è removeChildViewController: in -(void)viewDidDisappear:(BOOL)animated. Tuttavia, come staff di Apple mentioned, questa soluzione alternativa non è consigliata.

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