在以下示例中,我将展示一个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已泄露(更重要的是,它也会泄漏其主视图控制器和详细视图控制器)。
答案 0 :(得分:4)
是的,Apple员工在iOS 9中存在保留周期错误confirmed。
我已经测试过iOS 8.4中不存在保留周期,但在iOS 9.0和9.1中确实存在。 从iOS 9.2(在iOS 9.2模拟器上的Xcode 7.2 beta 2中测试)漏洞似乎已经修复了我已经整理了一个sample project来轻松确认是否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(在iOS 9.2模拟器上的Xcode 7.2 beta 2中测试)后漏洞似乎已修复
答案 1 :(得分:0)
据我所知,-[UIViewController addChildViewController:]
iOS 9.0~9.1
中存在内存泄漏问题。所以我认为它不仅仅是UISplitViewController的错误。 Snipets如下,
- (void)viewDidLoad {
[super viewDidLoad];
MyFirstViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"MyFirstViewController"];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
}
如果你从当前的视图控制器撤退,你会发现没有调用MyFirstViewController的dealloc。
可能的解决方法是在代码中使用storyboard的容器视图而不是addChildViewController
。我已经确认Container View的子视图控制器将被正确释放。
另一种解决方法是removeChildViewController:
中的-(void)viewDidDisappear:(BOOL)animated
。但是,作为苹果的工作人员mentioned,不推荐使用此解决方法。
- (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];
}
}