iOS SDK 中 UIScrollView 的 EXC_BAD_ACCESS 崩溃问题

36

我有一个iPhone SDK应用程序,其中有几个视图在用户创建内容时出现和消失。在设备上使用该应用程序一段时间后,我遇到以下崩溃:

Program received signal:  “EXC_BAD_ACCESS”.
(gdb) backtrace
#0  0x33369ebc in objc_msgSend ()
#1  0x320e5248 in -[UIScrollView(UIScrollViewInternal) _scrollViewAnimationEnded] ()
#2  0x338b4a14 in -[NSObject performSelector:withObject:] ()
#3  0x320e5098 in -[UIAnimator stopAnimation:] ()
#4  0x320e4b7c in -[UIAnimator(Static) _advance:] ()
#5  0x320e4a34 in LCDHeartbeatCallback ()
#6  0x34350e60 in HeartbeatVBLCallback ()
#7  0x332e91c0 in IOMobileFramebufferNotifyFunc ()
#8  0x316532f8 in ?? ()
#9  0x33866b50 in __CFMachPortPerform ()
#10 0x338ae52a in CFRunLoopRunSpecific ()
#11 0x338adc1e in CFRunLoopRunInMode ()
#12 0x3434e1c8 in GSEventRunModal ()
#13 0x32002c30 in -[UIApplication _run] ()
#14 0x32001230 in UIApplicationMain ()
#15 0x00002ff8 in main (argc=1, argv=0x2ffff550) at /Developer/svn/MyCompany/iPhone/MyApplication/Other Sources/main.m:14

从跟踪记录中可以看出,唯一提到我的代码的地方是对main函数的调用。

我已经从Xcode运行了Build和Analyze,并且还设置了从终端运行clang分析器来分析我的项目,但是这两种方式都找不到代码中的任何问题。我正在使用一个非常新的iOS SDK(我还没有下载4.1版本,但我使用的那个是在4.1版本之前发布的)。

此外,我已经在模拟器上使用Instruments运行了应用程序,而且应用程序没有内存泄漏。

我即将尝试使用NSZombieEnabled变量,看看是否能找到问题,但问题是我需要使用应用程序30至40分钟左右才会崩溃,我怀疑NSZombieEnabled甚至无法帮助我找到这个问题。

似乎我看到的崩溃是当模态视图调用父视图控制器中的委托时发生的。然后父视图控制器会执行一些处理操作,然后关闭模态视图控制器。在崩溃中有一些关于动画和滚动视图的引用,但我不确定我可能会做什么导致出现这些问题。有人有任何建议吗?

编辑:我已经将NSZombieEnabled标志放入应用程序中,在设备上,控制台出现了以下消息:

2010-09-11 17:10:33.970 MyApplication[9321:207] *** 
-[MyViewController respondsToSelector:]: message 
sent to deallocated instance 0x7489480
据我了解,我在我的所有类的dealloc中将应用程序使用的委托设置为nil,所以我不知道接下来该从哪里开始查找。
我尝试在此上使用 pid地址命令,但它说找不到该进程。我尝试了9321、9321:207和207。另外,如果我尝试使用变量,则程序无法在设备上运行,我会在控制台中得到一堆无法创建堆栈日志目录消息和程序崩溃。
顺便说一下,我无法使用Instruments中的僵尸检查,因为它似乎无法与设备一起使用,而且我无法在模拟器中出现相同的崩溃。
9个回答

61

我刚刚自己解决了这个问题。

我的问题是:

  • 将一个滚动视图代理连接到UIViewController上
  • 滚动视图开始进行动画
  • 代理消失并调用dealloc方法

问题在于,滚动视图代理消息在新释放的对象上触发,崩溃日志有点混乱,因为它们指向无意义的对象引用。

解决方法是将滚动视图代理设置为view controller的dealloc方法的第一行为nil。

希望这能帮助其他人!


5
你可能不知道你的回答为我解决了多大的麻烦,谢谢:D - user1243584
谢谢你的回答!省了我很多时间。 - derpoliuk
我之前遵循了Chips的答案,但这个更有意义,帮助我保持代码整洁。谢谢。+1 - Avi

42

第1个堆栈框架上的UIScrollView可能想要通知其代理动画已结束,但此时代理已经消失。设置NSZombieEnabled可能会证实这一点。

在Cocoa和Cocoa Touch中,代理并不会被保留,因此这是一个常见错误。在您的代码中查找UIScrollViewUITableView上的代理对象,并尝试找出哪一个可能在其生命周期之前就被释放了。


+1. 明确地说,委托属性应该始终设置为assign而不是retain - Shaggy Frog
规则总有例外。请参考NSURLConnection,了解保留委托的有效原因。 - Nikolai Ruhe
我有一个UITableView位于UIViewController上,当应用程序崩溃时,我相信它准备返回到这个视图控制器。但是,这个UITableView的代理是在Interface Builder中设置的,并且没有在代码中更改过。 - BP.
1
这个代理可能是问题所在。为了验证,请使用NSZombiesEnabled。你必须确保代理比UITableView存在更长的时间(或者将table view上的属性设置为nil)。当然,IB没有错,因为它不处理生命周期。你必须在你的应用程序逻辑中做这个。 - Nikolai Ruhe

16

为了完整性,我在这里添加了这个堆栈跟踪 (iOS 6),针对那些可能遇到同样问题但实现稍有不同且需要重现问题的人。

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x71f05631
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x3919b5d0 objc_msgSend + 1
1   UIKit                           0x33421830 -[UIScrollView(UIScrollViewInternal) _delegateScrollViewAnimationEnded] + 48
2   UIKit                           0x334217ba -[UIScrollView(UIScrollViewInternal) _scrollViewAnimationEnded:finished:] + 130
3   UIKit                           0x334216a4 -[UIAnimator stopAnimation:] + 460

在iOS 6上发生了这种情况,当我实现UIScrollViewDelegate方法时开始出现:

" -(void)scrollViewDidEndDecelerating:(UITableView *)tableView" 
and made a call to:
"[tableView scrollToRowAtIndexPath: indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];".  

当动画开始并且我按下“返回”按钮时,我的视图控制器在动画完成之前就被弹出了,导致了这个问题。
重现此问题时,请务必确保在动画开始后但动画完成之前按下“返回”按钮。我尝试通过编程方式弹出视图控制器来重新创建此问题,但未能重现它。我必须使用“返回”按钮。我一直在dealloc中简单地调用[myTableView release]。解决方案如此描述,需要将这两个属性都设置为nil:
self.myTableView.delegate = nil;
self.myTableView = nil;

7

首先,委托应该是弱引用类型。但即使在这种情况下,由滚动动画驱动的一个非常常见的微妙障碍也会出现。如果您对ScrollViews使用动画内容偏移更改,那么您强烈需要在dealloc方法中将其delegate设置为nil

否则,您将会得到以下结果:

[YourViewController respondsToSelector:]: message sent to deallocated instance

一个非常常见的例子:

1. _tableView is ivar of YourViewController
2. _tableView.delegate = self;
3. - (void)scrollViewDidScroll:(UIScrollView *)scrollView is implemented at YourViewController
4. at some point you call [_tableView scrollToRowAtIndexPath:indexPath 
   atScrollPosition:UITableViewScrollPositionBottom animated:YES];
   or [_tableView setContentOffset:CGPoint animated:YES]
   and try to close YourViewController

_tableView被CoreAnimation保留,但YourViewController已被释放!

1
我有一个类似的问题,我认为...它在scrollView didScroll代理方法中。但现在它是一个ARC项目,那怎么可能发生呢? - logixologist

3

我猜测scrollview的代理被设置为一个已经被释放的对象。在你的dealloc方法中尝试将所有子对象的代理设置为nil。


2

以上所有方法都没有解决我的问题,所以我再次深入检查了我的代码。

我发现当我执行键盘和UICollectionView动画(是的,这是一个聊天应用)并关闭当前控制器时,会出现崩溃。

应用程序崩溃是因为我尝试在动画完成块中进行滚动 :)

只需删除它,现在一切都正常工作!

愉快的编码和调试:)


2
如果您将刷新控制器作为子视图插入表格视图中,可能会发生这种情况(我的提示是,永远不要这样做)...

你有任何证据吗?我已经做出了更改,但我不确定它是否会修复崩溃。 - mOp
根据我的经验(而且也有同样的问题),由于旧版本的API使用了assign(而不是新的weak,因此会与野指针出现死锁) - 如果您将UIRefreshController作为子视图插入,那么这肯定是个问题。(永远不要这样做!UIRefreshController不应该用作子视图插入) - Peter Lapisu
我遇到了同样的错误,并怀疑问题是将UIRefreshController作为UITableView对象的子视图插入。根据Apple文档,UIRefreshControl只应与UITableViewController一起使用,但我正在处理一个旧版的UIViewController。经过进一步调试,我发现问题是应用程序在UIViewController中失去了对在其中一个UITableViewDelegate方法中创建的对象的引用。我所需要做的就是在UIViewController中创建一个属性,并将其设置为该对象。 - Vee

1
在面对相同的问题后,我设置了:


self.collectionView.delegate = nil;

在实际将 ViewController 设置为 collectionView 的代理之前,在 - (void)viewDidLoad 方法中,以及在 -(void)viewWillDisappear:(BOOL)animated 方法中,一切都正常工作。
感谢您的帮助。

0

我曾经遇到过这样的情况,当调用 scrollToRowAtIndexPath 时,使用了不存在的 indexPath。


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