View Controller即使已被解除分配也会收到消息

时间:2009-10-18 18:40:43

标签: ios objective-c iphone memory-management uiviewcontroller

我不确定iPhone SDK 3.0中是否有任何变化,但我收到了最奇怪的错误。我有一个视图控制器层次结构,我根据接口方向在视图控制器之间切换。据我所知,每当我旋转接口时,就会发生错误,已经解除分配的视图控制器正在发送一个shouldAutorotateToInterfaceOrientation消息。这是错误的回溯:

#0 0x01dc43a7 in ___forwarding___
#1 0x01da06c2 in __forwarding_prep_0___
#2 0x002e6733 in -[UIWindow _shouldAutorotateToInterfaceOrientation:]
#3 0x002e6562 in -[UIWindow _updateToInterfaceOrientation:duration:force:]
#4 0x002e6515 in -[UIWindow _updateInterfaceOrientationFromDeviceOrientation]
#5 0x0004d63a in _nsnote_callback
#6 0x01d8f005 in _CFXNotificationPostNotification
#7 0x0004aef0 in -[NSNotificationCenter postNotificationName:object:userInfo:]
#8 0x0045b454 in -[UIDevice setOrientation:]
#9 0x002d6890 in -[UIApplication handleEvent:withNewEvent:]
#10 0x002d16d3 in -[UIApplication sendEvent:]
#11 0x002d80b5 in _UIApplicationHandleEvent
#12 0x024c2ef1 in PurpleEventCallback
#13 0x01d9bb80 in CFRunLoopRunSpecific
#14 0x01d9ac48 in CFRunLoopRunInMode
#15 0x024c17ad in GSEventRunModal
#16 0x024c1872 in GSEventRun
#17 0x002d9003 in UIApplicationMain
#18 0x00002d50 in main at main.m:14

使用NSZombieEnabled打印到调试控制台的错误是:

2009-10-18 20:28:34.404 Restaurants[12428:207] *** -[ToolbarController respondsToSelector:]: message sent to deallocated instance 0x3b2b2a0
(gdb) continue
Current language:  auto; currently objective-c
2009-10-18 20:31:43.496 Restaurants[12428:207] *** NSInvocation: warning: object 0x3b2b2a0 of class '_NSZombie_BeltToolbarController' does not implement methodSignatureForSelector: -- trouble ahead
2009-10-18 20:31:43.496 Restaurants[12428:207] *** NSInvocation: warning: object 0x3b2b2a0 of class '_NSZombie_BeltToolbarController' does not implement doesNotRecognizeSelector: -- abort

我无法理解的是系统为什么会尝试向该控制器发送消息,即使它已被解除分配,并且有办法告诉系统控制器不再存在。

[UPDATE]: 我已经整理了一个复制错误的示例项目:download

加载应用程序,然后将“模拟器”的方向从“横向”更改为“纵向”几次,它应该会发生。我在物理手机上尝试了相同的代码,但行为完全相同,所以这不是模拟器相关的问题。

[UPDATE]: 我已经用掉了Apple的技术团队提出的一项支持请求,看看他们是否可以帮我解决这个问题。将发布解决方案 - 如果他们有一个 - 在这里。感谢您的帮助。

9 个答案:

答案 0 :(得分:4)

显然,控制器仍在注册相关通知。将-removeObserver:self发送到-dealloc方法中的通知中心。

答案 1 :(得分:4)

经过一周的等待后,Apple Developer Technical Support设法帮我解决了问题。以下是他们的回复:

  

“我查看了你的代码,发现了   你需要关心的一些事情   关于,其中一些可能有助于   你的问题。在你的   ControllerSwitchAppDelegate.m   来源,你正在实施   “didRotate”方法。有可能   值得检查设备方向   视图控制器上的通知   水平而不是UIApplication   水平。这将使你的代码更多   更简单和封装允许   显示的每个视图控制器   处理自己的旋转逻辑。您   也在使用多视图   控制器同时,那   存在,两个“视图”属性   在设备上添加和删除   旋转。这不完全是   使用的常用方法   UIKit的。想法是提出一个   视图控制器(或其视图属性)   一次,没有父视图   控制器交换在不同的子视图中   控制器。在表面上授予   你的方法似乎可行,但在   长远来看,我推荐一个不同的   方法

     

我们有一个叫做的样本   “AlternateViews”,可以找到   在 -       http://developer.apple.com/iphone/library/samplecode/AlternateViews/index.html

     

在这个样本中,它几乎可以   你需要什么。它提供了一个   “备用”视图控制器   给定设备方向。仅仅是   提出一个视图控制器   另一个使用   “presentModalViewController”带有   过渡属性叫做   “modalTransitionStyle”将给出   你是一个交叉淡化的影响。“

我最终做的是使用提供和解除视图控制器的超级视图控制器。而不是使用AppDelegate交换视图控制器和删除子视图。

答案 2 :(得分:2)

这里有很多混乱......让我们看看这是否有帮助:

  

我无法理解的是为什么   系统试图传达此信息   控制器,即使它已经   解除分配,有没有办法告诉   控制器没有的系统   已经存在了。

虽然解除分配会破坏对象的实例,但它不会破坏对实例的引用。启用Zombie检测后,取消分配会导致运行时将僵尸替换为实例。然后,僵尸会在收到消息时检测并记录。

发生这种情况的原因是因为对象已被释放,而且还没有从应用程序的对象图中删除对该对象的所有引用。在这种情况下,看起来释放的控制器永远不会被设置为UIWindow实例中的控制器。

也就是说, 通知的处理是红鲱鱼 。在该回溯中,通知已经传递,UIWindow正在处理它。因此,问题出在UIWindow和您的应用程序之间。最有可能的是,在作为其控制器或委托从窗口中删除之前,您的对象已被解除分配

在您的程序真正完成某个对象之前 - 在发送最后一次-release调用之前,该调用会平衡您的应用程序导致或调用的最后一个-retain - 您必须确保所有弱引用对象被破坏了。例如,如果您的对象是UIWindow的委托,请确保在发送最后nil之前将窗口的委托设置为-release(或其他某个对象)。

现在,在这种情况下,也可能只是你过度释放了对象。你可能仍然需要周围的对象,但是在你完成它之前,某个额外的-release-autorelease会导致它被销毁。

答案 3 :(得分:2)

如果有人还在乎,一个简单的解决方案就是创建一个永不改变的根视图控制器+视图。

鉴于SomeViewController + SomeView A和SomeViewController + SomeView B,如果您将视图A作为子视图添加到窗口,然后将视图B添加为子视图并删除视图A,应用程序将在旋转时崩溃。

为防止崩溃,请创建一个通用的UIViewController + UIView X,并将视图X作为子视图添加到窗口中。在视图X中添加和删除视图A和B,而不是直接从窗口中删除视图。该应用程序将不再崩溃。

请注意,仅仅向窗口添加视图是不够的;您必须添加具有视图控制器的视图。

答案 4 :(得分:1)

这是在释放后设置ToolbarController = nil的一个很好的理由。将消息发送到nil是安全的,但不能发送到解除分配的对象的地址。在这种情况下,您将消息发送到未退出的对象的地址,这会导致崩溃。

在发送邮件之前检查ToolbarController != nil是浪费时间,因为如果它是零,则可以安全地发送邮件。如果它不是零且有效,那么它将返回YES或NO。但如果它是一个指向释放内存的指针(比如你似乎在这里)它无论如何都会崩溃。

答案 5 :(得分:1)

我也遇到过这个问题。事件的顺序是:

(1)创建应用程序的单个UIWindow对象

(2)将由视图控制器管理的子视图添加到窗口

(3)删除第一个视图并添加一个新视图

如果我之后旋转设备,应用程序会因发送到解除分配的视图控制器的消息而崩溃。 (好吧,它实际上是将它发送到第一个视图控制器的子控制器。)它正在尝试发送 - [respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]。

如果您的应用程序仅以纵向模式运行,则可以通过向UIWindow添加一个覆盖_shouldAutorotateToInterfaceOrientation的类别来解决问题:为纵向模式以外的所有内容返回NO。

显然,这不是一个真正的解决方案。我已经对我的代码进行了两次三重检查,我发现没有理由为什么窗口应该将此消息发送到控制器以获取已从屏幕上删除并取消分配的视图。这个问题似乎也出现在3.0而不是之前。也许我正在做一些愚蠢的事情,但在这里工作似乎确实有些奇怪。

答案 6 :(得分:1)

我也遇到了同样的问题但是由于马克史密斯的建议而无法让控制器徘徊。使用自动释放而不是释放或保留属性删除视图控制器似乎可以解决问题。

似乎父UIWindow / framework需要视图控制器稍微停留一段时间以允许它删除委托链接。

答案 7 :(得分:0)

我一直遇到同样的问题,直到删除了一些我用来推动动画的'​​碰撞'代码行:

UIView* superv = navigationController.view.superview;

[navigationController.view removeFromSuperview];

[superv addSubview:navigationController.view];

当然,自从Apple发布3.0 SDK以来,上述方法已经“突破”了。我被迫使用推/弹方式代替。也没有2.x的问题。确保代码中没有类似的东西。

希望,这有帮助。

答案 8 :(得分:0)

我在这里发了一个答案:

https://stackoverflow.com/a/19237139/539149

我有个地方说:

[viewController release];
viewController = NULL;

导致释放被调用两次(因此内存被立即释放)但是直到iOS拥有的对象试图在主线程中稍后引用该对象时才显示僵尸。 < / p>