UIView的addSubview方法是否真的会保留(retain)视图?

12

我遇到了一个似乎表明相反的情况。在下面的代码片段中,如果我删除这行代码:self.navigationController = nav,那么根控制器的视图将不会显示出来,这让我觉得addSubview可能并没有像其他人所说的那样保留视图。有什么想法吗?

- (void)applicationDidFinishLaunching:(UIApplication *)application {   
   self.testViewController = [[TestViewController alloc] initWithNibName:@"TestView" bundle:  [NSBundle mainBundle]];

   UINavigationController *nav = [[UINavigationController alloc]  initWithRootViewController:self.testViewController];

   self.navigationController = nav;  //<-- if this line is removed, test view won't show up

   [window addSubview:nav.view];

   [nav release];
}

你标记的答案不正确,原因我在下面评论了。 - Corey Floyd
3个回答

24

这一行:

[window addSubview:nav.view];

该方法不会立即将视图添加到屏幕上,而是由操作系统在未来的某个运行循环中显示,可能在不同的线程上。我们无法确定实际的实现方式。

这就是为什么苹果定义了像viewDidAppear/viewWillAppear这样的代理方法,否则我们将不需要它们,因为我们会精确知道这些事件发生的时间。

此外,像你所说的添加子视图确实会保留视图,但不会保留视图控制器或导航控制器。由于导航控制器将保留任何添加的视图控制器,因此我们不必用ivar来支持它们。

但是,与导航控制器相关的引用必须在方法的范围之外保持持久性,否则根据您的代码,它可能会被释放或引用丢失。

因此,您必须使用ivar保留对导航控制器的引用,并进行如下设置:

self.navigationController = nav; 

因此,即使nav.view包含指向testViewController.view的指针,应用程序也没有引用导航控制器和视图。结果是一个空白屏幕。


为了更明显地表明这不是保留/释放问题,您实际上是在以下方法中泄漏:

self.testViewController = [[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]];
你需要使用autorelease来平衡你的retain/releases,方法如下:
self.testViewController = [[[TestViewController alloc] initWithNibName:@"TestView" bundle: [NSBundle mainBundle]] autorelease];

那意味着你的视图在你运行这段代码的任何时间都从未被释放。这进一步确认了你的问题确实是一个失去引用的问题。


1
_view_在窗口保留它之前不会被释放,这是错误的说法。如果是这样的话,当窗口尝试访问已释放的对象时,你会看到一个异常。实际上被释放的是导航控制器,这会阻止其将视图填充为有趣的内容(例如导航栏和根视图)。 - Daniel Dickison
经过更深入的思考,我添加了一些额外的信息,这也有助于解释你所看到的内容。 - Corey Floyd
谢谢,我说错了,我是指导航控制器,我会更新的。 - Corey Floyd
希望这是一个更好的答案。 - Corey Floyd
注意!在现代的Objective-C中,将值分配给self.testViewController很可能意味着您具有一个使用(nonatomic,retain)支持它的属性,因此这个错误不适用。换句话说,通常情况下,分配给self.something并不会引入泄漏,而这个解决方案则暗示了这一点。 - Andres Kievsky
显示剩余2条评论

2
问题可能不在于视图没有被保留,而是控制器没有被保留。
没有这行代码:
self.navigationController = nav

没有保留导航控制器。视图比控制器长久存在是很奇怪的。


视图并不是“神奇地”与视图控制器绑定在一起的。视图不会仅仅因为它的视图控制器死亡了而死亡。如果视图在其他地方被保留了,它绝对会比任何视图控制器更长寿。 - Corey Floyd
我并没有说它不会发生。我只是觉得,如果一个视图控制器比另一个活得更久,这很奇怪。特别是当这个视图是导航控制器的视图时,因为这是由导航控制器创建的一些内部视图层次结构。 - Jon Hess
如果你遵循惯例,那么你是正确的。我的观点仅在于这本身并不是一个理由。 这个唯一的原因通常是正确的,是因为你通常不会在从视图层次结构中移除其视图之前释放视图控制器。但是,在UIViewController中没有机制阻止你这样做,这只是源自适当的内存管理实践。 - Corey Floyd
Jon 的回答是最准确的。而 nav.view 被窗口保留,nav 本身则被释放和销毁。因此,导航控制器没有机会在其视图中放置任何有趣的东西(比如显示根视图控制器)。 - Daniel Dickison
很久以前,但是nav并没有被释放和解除分配。实际上它已经被释放了。然而,在方法的作用域之外,对nav的引用丢失了。这意味着当Cocoa渲染引擎在未来的运行循环中尝试绘制视图时,将会有一个无效的指针。 - Corey Floyd

0
这对我来说不像是一个保留/释放问题。如果您注释掉self.navigationController = nav;,则您的视图将不会显示,因为在下一行中,[window addSubview:self.navigationController.view],您的self.navigationController属性将不会被设置。它可能是nil,或者会崩溃,但没有更多的代码无法确定。

当出现问题并不起作用时,我已将代码更新回原始代码,并不得不引入self.navigationController来保存对导航的引用。另外,你上面看到的就是所有的代码,没有其他的了。 - Boon
在Objective-C中,ivars的初始值为0。因此,self.navigationController将只是nil。 - Jon Hess

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接