iOS应用在恢复时崩溃

10

(请参见底部更新)

最近,我的iPhone应用程序在返回后台时出现了一个奇怪且罕见的崩溃。崩溃日志只包含系统调用:

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

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x34c715b0 objc_msgSend + 16
1   CoreFoundation                  0x368b7034 _CFXNotificationPost + 1424
2   Foundation                      0x34379d8c -[NSNotificationCenter postNotificationName:object:userInfo:] + 68
3   UIKit                           0x37ddfec2 -[UIApplication _handleApplicationResumeEvent:] + 1290
4   UIKit                           0x37c37d5c -[UIApplication handleEvent:withNewEvent:] + 1288
5   UIKit                           0x37c376d0 -[UIApplication sendEvent:] + 68
6   UIKit                           0x37c3711e _UIApplicationHandleEvent + 6150
7   GraphicsServices                0x36dea5a0 _PurpleEventCallback + 588
8   CoreFoundation                  0x3693b680 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 12
9   CoreFoundation                  0x3693aee4 __CFRunLoopDoSources0 + 208
10  CoreFoundation                  0x36939cb2 __CFRunLoopRun + 642
11  CoreFoundation                  0x368aceb8 CFRunLoopRunSpecific + 352
12  CoreFoundation                  0x368acd44 CFRunLoopRunInMode + 100
13  GraphicsServices                0x36de92e6 GSEventRunModal + 70
14  UIKit                           0x37c8b2fc UIApplicationMain + 1116
15  [MyAppName]                     0x00083d60 main (main.m:20)
16  [MyAppName]                     0x00080304 start + 36

这可能看起来像是在UIApplicationWillEnterForegroundNotificationUIApplicationDidBecomeActiveNotification上调用了一个僵尸对象(根据堆栈跟踪中的_handleApplicationResumeEvent和崩溃时间猜测),但是:

  1. 我的任何类都没有注册UIApplicationDidBecomeActiveNotification,只有一些永远存在的单例注册了UIApplicationWillEnterForegroundNotification
  2. 我进行了一些实验,结果发现发布UIApplicationWillEnterForegroundNotification[UIApplication _sendWillEnterForegroundCallbacks:]发送,并且在崩溃日志中没有出现。

对我来说,所有这些意味着我使用的某个库中存在错误,或者是系统错误,并且崩溃在iOS 5.1.1(发布构建)、iOS 6.0(发布构建)和iOS 6.0(调试构建)上发生过一次。我扫描了我正在使用并且可以访问源代码的每个库,它们既不注册UIApplicationWillEnterForegroundNotification也不注册UIApplicationDidBecomeActiveNotification。我无法访问的唯一库是TestFlight,但是崩溃发生在TestFlight的1.0和1.1版本上,并且我已经使用前者很长一段时间了,没有出现这样的问题。 总之,我不知道为什么会出现这个崩溃以及它来自哪里。有什么想法吗?

更新1

我通过使用通知中心回调和记录堆栈跟踪来更深入地调查了这个问题,感谢DarthMike和matt的帮助。我发现当UIApplicationResumedNotification通知作为从后台返回的一部分被触发时,就会出现这个确切的堆栈。猜猜——它是一些“私有”通知,并且它没有公共标识符对应物。它没有userInfo,其对象是UIApplication(与许多在此之前发布的其他通知相同)。显然,我不使用它,我可以访问源代码的任何库也不使用它。我甚至找不到在互联网上合理的提及!我也非常怀疑TestFlight是罪魁祸首,因为崩溃在调试模式下也会发生,而我不会在调试模式下“起飞”TestFlight。

这是接收UIApplicationResumedNotification的堆栈跟踪。偏移量都相同,但是具有恒定的字节偏移量(根据库的不同为2或4,可能是因为它是调试堆栈跟踪而不是发布):

0   [MyAppName]                         0x0016f509 NotificationsCallback + 72
1   CoreFoundation                      0x3598ce25 __CFNotificationCenterAddObserver_block_invoke_0 + 124
2   CoreFoundation                      0x35911037 _CFXNotificationPost + 1426
3   Foundation                          0x333d3d91 -[NSNotificationCenter postNotificationName:object:userInfo:] + 72
4   UIKit                               0x36e39ec7 -[UIApplication _handleApplicationResumeEvent:] + 1294
5   UIKit                               0x36c91d61 -[UIApplication handleEvent:withNewEvent:] + 1292
6   UIKit                               0x36c916d5 -[UIApplication sendEvent:] + 72
7   UIKit                               0x36c91123 _UIApplicationHandleEvent + 6154
8   GraphicsServices                    0x35e445a3 _PurpleEventCallback + 590
9   CoreFoundation                      0x35995683 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
10  CoreFoundation                      0x35994ee9 __CFRunLoopDoSources0 + 212
11  CoreFoundation                      0x35993cb7 __CFRunLoopRun + 646
12  CoreFoundation                      0x35906ebd CFRunLoopRunSpecific + 356
13  CoreFoundation                      0x35906d49 CFRunLoopRunInMode + 104
14  GraphicsServices                    0x35e432eb GSEventRunModal + 74
15  UIKit                               0x36ce5301 UIApplicationMain + 1120
16  [MyAppName]                         0x000aa603 main + 390
17  [MyAppName]                         0x000a41b0 start + 40

NotificationsCallback是一个“观察者”回调,我暂时只是为了调试添加的。

为了证明一点,我故意从我的某个对象中省略了一次removeObserver:调用,以生成一个僵尸/异常,并且堆栈跟踪仍然包括_CFXNotificationPost + 1426 ,接着就是带有objc_msgSend + 16的EXC_BAD_ACCESS崩溃,就像原始崩溃一样。 这意味着有人为UIApplicationResumedNotification注册了一个观察者,在观察者被释放之前没有将其删除。基于我从未注册过这样的通知这个事实,我可以认为这个崩溃不是我的错。但问题仍然存在——那么是谁的责任呢?我想知道到底是谁注册了这个通知...

更新2

虽然我仍在等待看看在我的应用程序的新版本中是否有任何更改,但我在之前的版本中又遇到了由此引起的崩溃。结果发现,无论是什么为UIApplicationResumedNotification注册,都会为其指定选择器_applicationResuming:。不过,我怀疑这没什么用处。


1
对我来说,这意味着我正在使用的某个库中存在一个错误,或者是系统错误。我将给你最好的Cocoa Touch编程建议,假设这是 - matt
1
如果您正在使用Localytics,则这是其库的已知问题,他们已经发布了紧急更新以解决此问题。 - Lefteris
谢谢,Leftertis,但我不使用Localytics,我使用TestFlight。 - Vlas Voloshin
对于这种错误,我通常会按照以下步骤进行处理:逐步删除应用程序的部分,直到崩溃不再发生。您是否可以轻松地删除TestFlight使用、删除其他库或甚至删除代码中的addObserver调用?逐个删除这些内容应该会揭示问题所在。如果是框架问题,则在没有任何对象注册任何通知的情况下,崩溃应该会发生。 - DarthMike
我明白你的意思,你的代码中从未注册崩溃通知,对吗?如果有的话,你可以展示一下注册/注销通知的代码吗?由于使用了很多库和依赖项,找到问题所在可能会很困难,就像你所说的那样。我认为你是正确的,某些对象可能没有正确地取消观察者的注册,当然这不一定是你代码中的问题。 - DarthMike
显示剩余2条评论
5个回答

6
我在一份 IOS 6.0.1 设备崩溃报告中看到了完全相同的堆栈跟踪信息。我按照以下步骤在模拟器中重现了此问题:
  • 将应用程序置于后台
  • 从模拟器菜单中模拟内存警告
  • 将应用程序带回前台
经过大量调试,我发现一个 UITextField 在响应内存警告时会发送_applicationResuming:消息。在 IOS 5.1 下进行相同的模式测试没有引起崩溃。但由于某些原因,在 IOS 6 中,UITextField 注册了 ApplicationResumeEvent(可能不总是注册,但在键盘出现后注册)。
我的解决方法是在释放对象之前从 NSNotificationCenter 中移除它。
[[NSNotificationCenter defaultCenter] removeObserver:self.placeFld];
self.placeFld = nil;

哇,如果我能在我的情况下确认这一点,那么对于那些仍然认为这是我的应用程序出现问题的人来说,这将是一个重大的“打脸”。由于这个问题,我仍然会收到偶尔的崩溃报告,所以我会尝试按照你描述的方式复现它。不过我已经转向另一个项目了,所以可能需要一些时间...无论如何,还是谢谢! - Vlas Voloshin
我不得不在-[UIApplication _handleApplicationResumeEvent:]和__CFNotificationCenterAddObserver_block_invoke_0中使用汇编代码断点进行艰难的调试会话。然后,我可以使用$ecx寄存器中的地址检查接收通知的对象。我在没有模拟内存警告的情况下经历了这样的会话(使用列表获取接收通知的对象(po $ecx将为您提供UITextField的内容,使识别更加容易)。然后再次进行具有内存警告的会话。对象以相同的顺序接收通知。 - Periklis Konstantinidis

3

我刚遇到了这个问题,并找到了一个解决方案,不需要删除通知。在我们的情况下,有旧代码正在执行以下操作:

- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
  [searchBar resignFirstResponder];
  // other stuff
}

我不知道为什么会出现这个问题,但现在已经解决了,崩溃也消失了。看起来,在这种情况下,在调用searchBarTextDidBeginEditing时先撤销第一个响应者会使搜索栏文本编辑字段上的通知成为孤儿,并且当拥有此UISearchBar的视图控制器被释放并且我们进行后台/前台切换时,我们就会崩溃。

可能会因人而异。


1
实际上,我的应用程序中有几个地方做的事情几乎相同!我已经尝试在iOS 6.0模拟器上重现相同的情况 - 结果证明确实如此!很遗憾我不再从事这个应用程序的工作,但了解这个错误仍然是值得的,所以谢谢!我肯定会搜索一下是否已经记录了这个错误,如果没有 - 我会发布一个错误报告。 - Vlas Voloshin
我在这里还应该注意一点,即不要尝试在searchBarTextDidBeginEditing中重新指定第一响应者,而是应该实现searchBarShouldBeginEditing并在搜索栏应该保持不变的情况下返回NO。虽然没有任何文档说明我不能在searchBarTextDidBeginEditing中调用resignFirstResponder,但对于简单的UITextField,同样的技巧可以毫无问题地工作。 - Vlas Voloshin
顺便说一下,我可以确认这个问题已经在我们最喜欢的移动操作系统的最新版本中得到了解决,由于保密协议的原因,我可能不能提及具体的名称 :) - Vlas Voloshin

2

-[NSNotificationCenter postNotificationName:object:userInfo:]上设置一个断点。它试图向一个不存在的对象发送通知,或者类似于此类的情况。您可能会管理自己的通知或自己的对象不当。

如果您尚未使用ARC,请考虑切换到ARC。

使用静态分析器。它可以找到潜在的内存问题。


谢谢,我已经使用断点和“通配符”观察器回调来遍历所有生成的通知,并记录堆栈跟踪日志,请参见我的问题更新中的结果。是的,我已经在使用ARC并定期运行静态分析器了。可能有必要提及一下,我正在处理的这个应用程序将近一年的时间了,我已经见过许多种类的崩溃并修复了它们... - Vlas Voloshin

1

可能有很多原因,但我认为检查代码中注册任何UIApplication通知的人会更好。您真的不知道哪个通知触发了错误。

还有,是否有任何对象在保留/持有AppDelegate的强引用?这可能会导致一些奇怪的保留周期使此崩溃发生。

我从未见过XCode行为不当导致这样的崩溃。

编辑:粘贴头文件中的所有通知。可能有点过度,但有些通知可能会在应用程序恢复/从后台发送

UIKIT_EXTERN NSString *const UIApplicationDidEnterBackgroundNotification       NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationWillEnterForegroundNotification      NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationDidFinishLaunchingNotification;
UIKIT_EXTERN NSString *const UIApplicationDidBecomeActiveNotification;
UIKIT_EXTERN NSString *const UIApplicationWillResignActiveNotification;
UIKIT_EXTERN NSString *const UIApplicationDidReceiveMemoryWarningNotification;
UIKIT_EXTERN NSString *const UIApplicationWillTerminateNotification;
UIKIT_EXTERN NSString *const UIApplicationSignificantTimeChangeNotification;
UIKIT_EXTERN NSString *const UIApplicationWillChangeStatusBarOrientationNotification; // userInfo contains NSNumber with new orientation
UIKIT_EXTERN NSString *const UIApplicationDidChangeStatusBarOrientationNotification;  // userInfo contains NSNumber with old orientation
UIKIT_EXTERN NSString *const UIApplicationStatusBarOrientationUserInfoKey;            // userInfo dictionary key for status bar orientation
UIKIT_EXTERN NSString *const UIApplicationWillChangeStatusBarFrameNotification;       // userInfo contains NSValue with new frame
UIKIT_EXTERN NSString *const UIApplicationDidChangeStatusBarFrameNotification;        // userInfo contains NSValue with old frame
UIKIT_EXTERN NSString *const UIApplicationStatusBarFrameUserInfoKey;                  // userInfo dictionary key for status bar frame
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsURLKey                NS_AVAILABLE_IOS(3_0); // userInfo contains NSURL with launch URL
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsSourceApplicationKey  NS_AVAILABLE_IOS(3_0); // userInfo contains NSString with launch app bundle ID
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsRemoteNotificationKey NS_AVAILABLE_IOS(3_0); // userInfo contains NSDictionary with payload
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsLocalNotificationKey  NS_AVAILABLE_IOS(4_0); // userInfo contains a UILocalNotification
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsAnnotationKey         NS_AVAILABLE_IOS(3_2); // userInfo contains object with annotation property list
UIKIT_EXTERN NSString *const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationProtectedDataDidBecomeAvailable    NS_AVAILABLE_IOS(4_0);
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsLocationKey           NS_AVAILABLE_IOS(4_0); // app was launched in response to a CoreLocation event.
UIKIT_EXTERN NSString *const UIApplicationLaunchOptionsNewsstandDownloadsKey NS_AVAILABLE_IOS(5_0); // userInfo contains an NSArray of NKAssetDownlo

谢谢,实际上值得一试!顺便说一下,我怀疑是 UIApplicationWillChangeStatusBarFrameNotification ... 很快就会调查。 - Vlas Voloshin

-1
在调试奇怪的崩溃之前,我会做一些准备工作。
进行构建清理(清除本地缓存文件)。
从模拟器/设备中删除应用程序(有时XIB被缓存)。
重新启动Xcode(当Xcode与当前设置不同步时存在一些奇怪的错误)。
然后再尝试一次。

谢谢,但这个问题存在于调试和发布(包括App Store和Ad-hoc)配置中,我已经在其中多次进行了清理,所以我猜那不是原因。 - Vlas Voloshin

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