我该如何阻止HIToolbox捕获我的异常?

5

这个问题是基于我的另一个问题 为什么我的应用程序没有被异常关闭

问题

当通过一个 Action 在主线程抛出异常时,应用程序仍然不会崩溃。

根据Dave在我的原问题的回答,我实现了NSApplication上的reportException类别并设置了未捕获异常处理程序。

代码

我在我的应用程序委托中有以下代码,我已经将其连接到UI中的一个按钮进行测试。

-(IBAction)crashOnMainThread:(id)sender {
    [self performSelectorOnMainThread:@selector(crash) withObject:nil waitUntilDone:YES];
}

-(void)crash {
    // To test out the exception handling
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the app."];
}

当我按下按钮时,我的应用程序不会崩溃。当我查看控制台日志时,我看到了这个信息:
06/09/2010 14:12:25 EHTest1[26384]  HIToolbox: ignoring exception 'This should crash the app.' that raised inside Carbon event dispatch
(
    0   CoreFoundation                      0x00007fff80ab4cc4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x00007fff819560f3 objc_exception_throw + 45
    2   CoreFoundation                      0x00007fff80ab4ae7 +[NSException raise:format:arguments:] + 103
    3   CoreFoundation                      0x00007fff80ab4a74 +[NSException raise:format:] + 148
    4   EHTest1                             0x00000001000010e3 -[EHTest1_AppDelegate crashLapsus] + 63
    5   Foundation                          0x00007fff88957c25 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 234
    6   Foundation                          0x00007fff8896ad48 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 143
    7   EHTest1                             0x0000000100001030 -[EHTest1_AppDelegate crashOnMainThread:] + 60
    8   AppKit                              0x00007fff85c7e152 -[NSApplication sendAction:to:from:] + 95
    9   AppKit                              0x00007fff85ca26be -[NSMenuItem _corePerformAction] + 365

    ** Snip **

看起来Carbon捕获了异常,这真的很烦人。

这表明对于任何操作代码,您需要立即在后台线程中运行它,以便任何异常都被注册为未捕获异常。嗯?我没有看到过任何结构化的代码。

我尝试过的

通过延迟使应用程序崩溃,以便它不连接到UI元素。它可以正常崩溃。

我尝试使用以下代码在我的应用程序委托中安装自定义NSExceptionHandler:

-(BOOL)exceptionHandler:(NSExceptionHandler *)sender 
  shouldHandleException:(NSException *)exception 
                   mask:(unsigned int)aMask {
      abort();
      return YES;
}

-(void)applicationWillFinishLaunching:(NSNotification *)aNotification {
      NSExceptionHandler *handler = [NSExceptionHandler defaultExceptionHandler];
      [handler setExceptionHandlingMask:NSLogAndHandleEveryExceptionMask];
      [handler setDelegate:self];
}

问题在于无论异常是否被捕获,它都会崩溃。如果我尝试检查掩码并且不在捕获异常时崩溃,则又回到了原点,因为看起来HIToolbox以与try / catch块完全相同的方式捕获异常。
问题: 1.如何停止HIToolbox捕获异常,使我的应用程序使用未捕获的异常处理程序并崩溃? 2.在与操作相同的调用堆栈中运行代码是否可以?这肯定没问题吧? 3.如果不行,那么有什么替代方案?
这让我非常烦恼,所以任何帮助将不胜感激。

为什么要让您的应用程序在发生未捕获的异常时崩溃?这些异常已经被捕获并记录下来,以便应用程序不会崩溃。只需在[NSException raise]objc_exception_throw上设置断点以调试您的异常,无需在此处崩溃。 - Sven
1
这对于你正在调试的应用程序来说是可以的。但是当它在生产环境中出现问题时怎么办?我希望我的应用程序在出现问题的那一刻就崩溃,这样用户就可以报告问题,我也能及时知道并立即修复它。 - John Gallagher
1
只是提醒一下,这似乎不再是一个问题了(macOS 10.14)。如果您设置 [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];,就能够正确地捕捉到崩溃错误。 - dmaclach
2个回答

6

我已经回答了你上一个关于这个主题的问题,并且遇到了Carbon的HIToolbox捕获IBActions抛出的异常的同样问题。

首先,撤销我在之前的答案中提到的所有内容。由于某些原因,它与IBActions不兼容。我的直觉是,HIToolbox位于异常处理链的较低层,并在NSApplication有机会之前获取任何IBAction / GUI异常。您可以使用NSSetUncaughtExceptionHandler()注册自定义异常处理函数,该函数(我认为)位于链的顶部。

您正在正确的轨道上使用NSExceptionHandling:

  1. ExceptionHandling.framework添加到您的Xcode项目中
  2. #import "ExceptionHandlerDelegate.h" 到您的AppDelegate.m(或自定义单例异常类)中

AppDelegate.m中:

// Very first message sent to class
+ (void)initialize
{
    NSExceptionHandler *exceptionHandler = [NSExceptionHandler defaultExceptionHandler];
    unsigned int handlingMask = NSLogAndHandleEveryExceptionMask;
    [exceptionHandler setExceptionHandlingMask:handlingMask];
    [exceptionHandler setDelegate:self];

    // ...
}


#pragma mark -
#pragma mark NSExceptionHandler Delegate Methods

// Called 1st: Should NSExceptionHandler log the exception?
- (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldLogException:(NSException *)exception mask:(unsigned int)aMask
{
    // ...log NSException if desired...

    return NO;  // Changes to YES if NSExceptionHandler should handle logging
}

// Called 2nd: Should NSExceptionHandler handle the exception?
- (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldHandleException:(NSException *)exception mask:(unsigned int)aMask
{
    // ...crash your app here (e.g. [NSApp terminate:self]; )

    // ...or handle the NSException and return YES/NO accordingly

    return NO;  // If app crashed, never gets returned - should crash before that
}
NSLogAndHandleEveryExceptionMask标志告诉NSExceptionHandler捕获它能够捕获的每个异常(仅针对你的应用程序),无论它在异常链的哪个位置。
这意味着@catch/@try/@finally块将不起作用,因为NSHandleOtherExceptionMask标志告诉NSExceptionHandler捕获“它下面的所有内容”在异常处理程序链上。您可以删除该标志,但是HIToolKit可能会再次获取任何IBAction异常,因为它似乎在该链中较低。
苹果的文档有关于这些标志的信息:NSExceptionHandler docs 因此,当引发NSException时(我认为是在您的应用程序的任何地方),并且设置了NSLogAndHandleEveryExceptionMask,这些是按顺序在委托中调用的:
1. 首先调用-exceptionHandler:shouldLogException:mask: 2. 其次调用-exceptionHandler:shouldHandleException:mask: 只需将您的“崩溃代码”放在第二个委托方法中,您就应该没问题了。
有用的文章:Understanding Exceptions and Handlers in Cocoa 我认为您在使用NSExceptionHandler的委托时遇到问题的原因是它与使用NSSetUncaughtExceptionHandler()设置的自定义方法不兼容,这是我之前提问中答案的一部分。根据Apple的说法:
“NSExceptionHandler类提供了监视和调试Objective-C程序中异常条件的功能。它通过安装一个特殊的未捕获异常处理程序来工作,该处理程序通过NSSetUncaughtExceptionHandler函数设置。因此,要使用NSExceptionHandler的服务,您不能安装自己的自定义未捕获异常处理程序。”
当您覆盖NSApplication的-reportException:方法时,它也可能无法正常工作。
最后,似乎没有必要使用@catch/@try/@finally(也是我之前答案的一部分)。配置NSExceptionHandler内的+initialize似乎立即“启动”,而不像覆盖NSApplication的-reportException:方法。

哇,Dave。非常感谢你详细而清晰地回答了我的问题。我会在有时间的时候按照你的建议来修复我的应用程序。我会告诉你我处理的情况。需要消化的东西很多。再次感谢! - John Gallagher
很高兴能帮助你,John。 :) 如果您发现其他情况无法正常工作,请告诉我,这样我就可以修复我的代码。随时发送电子邮件至:firstname@firstname-lastname.net - Dave
直到我将初始化代码移动到applicationDidFinishLaunching应用程序委托方法中,我才能使代码正常工作,这是有道理的,因为initialize是一个类方法。此外,是否有任何方法可以使用异常调用系统崩溃报告器,而不仅仅是终止应用程序? - trojanfoe
由于从类方法调用了setDelegate:self,所以示例代码中的委托方法不会被调用。要么将两个委托方法也更改为类方法,要么将代码移动到实例方法中,其中self是实例而不是类。 - danielv

1

你不能可靠地这样做。除非有明确的文档说明(我想不出任何明确说明的情况),否则不支持在 API 边界上抛出异常。


那很有道理。但在这种情况下,如果HIToolbox先于我捕获异常,我该如何捕获和报告代码中的异常呢?如果我想要捕获异常,我得出结论是不应该从同一线程中运行代码,对吗?这对我来说似乎很奇怪。那么替代方案是什么? - John Gallagher

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