iOS 7.1 removeFromSuperview 崩溃

14

直到iOS 7.1推出前,我的应用从未崩溃。现在在任何removeFromSuperview方法中都会崩溃。例如:我有视图控制器,当我想要删除一个视图控制器时,我会先删除它的所有子视图,然后从堆栈中删除(堆栈:我将视图控制器存储在其中,以加载新内容和加载先前的内容):

    for (UIView *subView in [contentVc subviews])
         [subView removeFromSuperview];

我遇到了以下问题:

-[CALayer retain]: message sent to deallocated instance

请问这是什么意思?

[actual removeFromParentViewController];

这是一种好的方法吗?它会释放整个视图控制器及其子视图吗?因为我使用 removeFromSuperview 时,我的应用程序崩溃了。我不明白在 iOS 7.1 中发生了什么变化。

如果我想要移除一个 viewController 的所有子视图,但不想使用 removeFromSuperview,也不想移除我的 ViewController (如果我只想添加新的子视图,并删除当前内容),该怎么做呢?

更新:

有时会出现崩溃:

[myactualviewcontroller.view removeFromSuperview];

-[CALayer retain]: message sent to deallocated instance

为什么???

有时,如果我尝试从视图控制器视图中删除主子视图,它会崩溃:

[mainView removeFromSuperview] (mainView是单个UIView,添加到vc.view中)

更新2:(详细说明)

所以,我有一个容器视图。 我将UIViewController.view添加到此容器中。 然后,我将视图添加为UIViewController.view的子视图。 这个视图不是本地的uiview,我的意思是,它被声明为implementation { UIView * mainView }。当我的UIViewController被释放时,在其- (void) dealloc { [mainView removeFromSuperview]; [mainView release] [super dealloc];}中,执行mainView removeFromSuperview时,我的应用程序崩溃了。


2
除此之外,这段代码很好;您是否会对视图进行弱引用或不安全的引用? - Ian Henry
2
为什么你觉得在移除视图控制器时需要删除所有子视图?这完全没有必要。 - rmaddy
请查看以下链接: https://dev59.com/U2DVa4cB1Zd3GeqPclkU && http://iphonedevsdk.com/forum/iphone-sdk-development/98675-calayer-retain-message-sent-to-deallocated-instance.html - Kumar KL
在运行iOS 7.0和7.1时,使用NSLogging(并比较)子视图是否有帮助? - Vlad Smoc
@incmiko 这个问题解决了吗,还是仍然存在? - Bikramjit Singh
显示剩余11条评论
9个回答

7
通常在进行快速枚举时修改数组并不是一个好主意。你似乎正在使用快速枚举来遍历视图的子视图数组,并同时删除其中的子视图。您可以尝试使用以下代码:
NSArray *subviewsCopy = [[contentVc subviews] copy];
for (UIView *subview in subviewsCopy) {
    [subview removeFromSuperview];
}

然而,正如其他人提到的那样,需要手动删除这些子视图有点奇怪。在正常情况下,当视图控制器本身被释放时,视图控制器的视图(以及其下面的视图层次结构)将自动清理。
此外,还有一些好工具可用于帮助您跟踪问题的源头。特别是,您应该在Xcode中的“产品”菜单下对您的应用进行配置,并在Instruments提示时选择Zombies工具。使用Zombies工具,您可以查看在对象被释放后消息传递的保留/释放历史记录。
如果您尝试手动清除视图层次结构,因为您怀疑否则会泄漏您的视图,请尝试在Instruments中使用Leaks工具,并验证禁用此代码时相关视图是否实际泄漏。

我的子视图不是nil,也不是NSZombies。但如果我尝试将其从父视图中移除,它会崩溃。 - Magyar Miklós

6
你的程序崩溃是因为你释放了同一个东西多次。这一点很明显。
找到问题的第一步是在调试器中启用僵尸对象检测(项目->方案->编辑方案->诊断->启用僵尸对象)。目标在于让你的程序尽早崩溃。这将使你在尝试访问已释放实例时立即进入调试器。有时这会指向正确的方向,有时不会,但总体来说,越接近问题发生的位置检测到问题就越好。
下一步是使用Zombies工具。这个工具将比前一步提供更多信息,但使用起来更加复杂(这就是为什么我把它作为第二步而不是第一步的原因)。Zombies工具将跟踪所有的分配和释放,并检测当你尝试访问僵尸对象时。
最后的办法是开始注释掉代码。首先注释掉创建视图控制器(崩溃的那个)和释放它之间程序所做的所有事情。然后运行程序并执行使其显示错误视图控制器所需的任何操作。显然它什么也不会做,因为现在它只是一个空的视图控制器,但它不应该崩溃。然后开始逐步取消注释代码块,并在每个迭代之间保持运行状态。这是一个重复的过程,如果你的视图控制器代码很大而且复杂,可能会很烦人。但是,关键是要逐渐添加你的代码,直到添加某些内容并导致崩溃-那么你就知道哪一段代码引起了问题。在这里,你必须有创意,并仔细选择如何重新添加代码-如果你的程序具有良好的模块化设计,你应该能够轻松完成这个过程。如果你的程序是一个面条式代码,这将很难做到,但同时也可以给你一个重构代码的好机会。通过这个过程,你将缩小问题范围,并最终通过排除法找到错误。

非常感谢您的回答,但是我已经解决了这个问题,就像我在评论中写的那样。我使用了相同的机制:“我实现了一个自定义容器,并通过启用nszombie和使用instruments解决了我的随机崩溃问题。问题是有些东西被释放了两次,但令人遗憾的是,Xcode没有告诉我在哪里以及是什么。而且崩溃并不是在第二个释放行之后直接发生的。它会在几秒钟后完全随机地崩溃。现在它运行得很好。谢谢你的回答。” - Magyar Miklós
我投了你的答案一票,唯一的原因是为什么我没有删除我的问题:它可能对某人有用 :) - Magyar Miklós

2

sometimes crash for:

[myactualviewcontroller.view removeFromSuperview];
您不应该手动添加或移除控制器视图到视图层次结构中,而是依赖于UIWindowrootViewController,将您的控制器推到UINavigationController等,以便系统将视图添加到私有底层的superviews中。除非你正在创建一个自定义容器视图控制器,但我猜你不是这种情况。

如果您只想手动处理视图,请勿使用视图控制器,因为它们不会被系统保留,并且不会收到任何旋转消息等。所以在这种情况下使用视图控制器是毫无意义的。

至于子视图内存处理,子视图由其superview保留,因此只要您没有保持strong引用,您就不需要释放子视图,只需删除对应的共同superview即可。

再次强调,如果您正确使用视图控制器,释放控制器将清除所有视图。

最后,您应该开始使用ARC。


我实现了一个自定义容器,并通过启用“nszombie”和使用“Instruments”解决了我的随机崩溃问题。问题是有些东西被释放两次,但可悲的是,Xcode没有告诉我在哪里以及是什么。而且崩溃并不是在第二个释放行之后直接发生的,而是在几秒钟后完全随机地发生的。现在它正常工作了。谢谢你的回答。 - Magyar Miklós
我认为我不应该使用ARC,ARC所做的就是像你保留和释放一样,只是计数而已。但我确切地知道我不需要对象的具体点,并且我可以释放它。我可以编写有效的代码。ARC会决定对象引用计数是否为0并释放。我也是这样做的,但对象会一直存在直到拥有强引用(保留)或者直到大括号结束。但我知道在几行代码后我不再需要该对象了。 - Magyar Miklós
ARC不仅可以为您释放对象,还可以启用自动nil的“weak”引用,从而避免“unsafe_unretained”引用。此外,在大多数情况下,它比您更有效率,对于剩余的情况,如果需要,您可以使用@autoreleasepool{} - Rivera

2

已更新

尝试做到这一点:

NSArray *subviews = [NSArray arrayWithArray:[contentVc subviews]];
for (UIView *subView in subviews)
     [subView removeFromSuperview];

我认为你遇到了崩溃,因为你试图快速枚举一个长度可变的数组(实际上,当你移除子视图时,它也会从子视图数组中移除)。

如果你想要移除viewController,只需调用:

[contentVc.view removeFromSuperView];
[contentVc removeFromParentViewController];

4
您的意思是 NSArray *subviews = [[contentVc subviews] copy]; 吗?否则它没有区别。 - Bryan Chen

1
根据苹果的文档,调用removeFromSuperview将会从父视图中移除该视图并自动释放它。
因此,如果你正在使用removeFromSuperview,那么就不应该调用[removedView release],否则会导致你的应用程序崩溃。
参考苹果的截图。

enter image description here

在您的dealloc实现中,您会像这样写。
- (void) dealloc {

    // Removed from Parent view and released.
    [mainView removeFromSuperview];

    // no mainView exists in memory , so it crashed the App.
    [mainView release];// Comment this line to avoid the crash

    [super dealloc];
}

2.您不应该静音正在枚举的容器。

You are having like this,

for (UIView *subView in [contentVc subviews])
     [subView removeFromSuperview];

相反,您可以通过使用苹果的这一行代码来实现相同的效果。

[[contentVc subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

我必须在removeFromSuperview之后释放mainView,因为它是viewController的一个被保留的属性。问题不在于此。我已经解决了我的问题,并且一旦有足够的时间,我会回答我的问题。 - Magyar Miklós
当将mainView添加到父视图中时,它会被保留,因此这是错误的。 - Ahmad Al-Attal

1
请确保在删除视图之前,所有可能的委托都已移除(即在[someScrollView removeFromSuperview];之前设置someScrollViewDelegate = nil;),并且动画已完全完成(包括所有CATransaction[UIViev beginAnimation...][UIView animateWithDuration...]等)。

1

请按照以下步骤操作:

1- 调试 for (UIView *subView in [contentVc subviews]) 并检查它迭代了多少次。

如果在第一次迭代中没有崩溃,您可以在删除视图之前添加此行代码:if (subView.superView != nil)

否则,请确保您没有在其他地方两次释放视图,因为它会一直显示,直到从其父视图中删除为止才会崩溃。

更新2: 我将考虑您已经意识到内存泄漏,并且具有良好的经验。

每当您将子视图添加到视图中时,它将保留该对象1个计数,加上原始的1个计数,总共为2。然后,在添加子视图之后直接释放它,这将将保留计数减少到1个。这里有一个诀窍:您不必释放子视图或将其从其父视图中删除以消除剩余的保留计数。您可以简单地删除或释放父视图。以NSMutableArray为例。
从dealloc:方法中删除[mainView removeFromSuperview];。您可以在其他地方添加它,例如viewWillDisappear:方法。dealloc方法不应包含除释放调用以外的任何内容。

最酷的是这些子视图不为 nil,但如果我尝试移除它,就会崩溃。 - Magyar Miklós
不是关于子视图的空值,而是关于父视图,请尝试发送一条消息。 - Ahmad Al-Attal
UIView* view = [[UIView alloc]initWithFrame: self.view.frame]; [self.view addSubview: view]; [view release]; for (UIView* subview in self.view.subviews) { [subview removeFromSuperview]; [subview removeFromSuperview]; [subview removeFromSuperview]; }它没有崩溃。 - Ahmad Al-Attal

0

不是:

for (UIView *subView in subviews)
     [subView removeFromSuperview];

尝试:

[subviews makeObjectsPerformSelector:@selector(@"removeFromSuperview");

0
尝试先检查视图是否为nil,然后再调用removeFromSuperview 例如:
@IBOutlet weak var btnSNSApple: UIView! if self.btnSNSApple != nil { self.btnSNSApple.removeFromSuperview() }


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