答案 0 :(得分:10)
更新 - 2010年11月16日:在IBAction方法中引发异常时,此答案存在一些问题。请改为查看此答案:
How can I stop HIToolbox from catching my exceptions?
这扩展了 David Gelhar的答案以及他提供的链接。下面是我通过重写NSApplication的-reportException:
方法来做到这一点。首先,为NSApplication创建一个ExceptionHandling类别(仅供参考,您应该在“ExceptionHandling”之前添加一个2-3字母的缩写,以降低名称冲突的风险):
<强>的NSApplication + ExceptionHandling.h 强>
#import <Cocoa/Cocoa.h>
@interface NSApplication (ExceptionHandling)
- (void)reportException:(NSException *)anException;
@end
<强>的NSApplication + ExceptionHandling.m 强>
#import "NSApplication+ExceptionHandling.h"
@implementation NSApplication (ExceptionHandling)
- (void)reportException:(NSException *)anException
{
(*NSGetUncaughtExceptionHandler())(anException);
}
@end
其次,在NSApplication的委托中,我做了以下内容:
<强> AppDelegate.m 强>
void exceptionHandler(NSException *anException)
{
NSLog(@"%@", [anException reason]);
NSLog(@"%@", [anException userInfo]);
[NSApp terminate:nil]; // you can call exit() instead if desired
}
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSSetUncaughtExceptionHandler(&exceptionHandler);
// additional code...
// NOTE: See the "UPDATE" at the end of this post regarding a possible glitch here...
}
您可以拨打terminate:
,而不是使用NSApp的exit()
。 terminate:
更像是Cocoa-kosher,但是如果抛出异常并且仅仅使用applicationShouldTerminate:
进行硬碰撞,您可能希望跳过exit()
代码:
#import "sysexits.h"
// ...
exit(EX_SOFTWARE);
每当抛出异常时,在主线程上,并且它没有被捕获和销毁,现在将调用自定义未捕获异常处理程序而不是NSApplication。这使您可以使应用程序崩溃等等。
上面的代码中似乎有一个小故障。在NSApplication完成调用其所有委托方法之后,您的自定义异常处理程序将不会“启动”并继续工作。这意味着,如果您在 applicationWillFinishLaunching:或 applicationDidFinishLaunching:或 awakeFromNib:中执行了一些设置代码,则默认的NSApplication异常处理程序似乎位于 - 播放完全初始化之后。
这意味着如果你这样做:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSSetUncaughtExceptionHandler(&exceptionHandler);
MyClass *myClass = [[MyClass alloc] init]; // throws an exception during init...
}
您的 exceptionHandler 不会获得异常。 NSApplication将会,它只会记录它。
要解决此问题,只需将任何初始化代码放入@try/@catch/@finally
块中,您就可以调用自定义 exceptionHandler :
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSSetUncaughtExceptionHandler(&exceptionHandler);
@try
{
MyClass *myClass = [[MyClass alloc] init]; // throws an exception during init...
}
@catch (NSException * e)
{
exceptionHandler(e);
}
@finally
{
// cleanup code...
}
}
现在您的exceptionHandler()
获得了异常并可以相应地处理它。在NSApplication完成调用所有委托方法之后, NSApplication + ExceptionHandling.h 类别启动,通过其自定义-reportException:
方法调用exceptionHandler()。此时,当您希望将异常提升到未捕获的异常处理程序时,您不必担心@ try / @ catch / @。
我对导致这种情况的原因感到有些困惑。可能是API中幕后的东西。即使我将NSApplication子类化,而不是添加类别,也会发生这种情况。此外,可能还有其他警告。
答案 1 :(得分:6)
原来是一个非常简单的解决方案:
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];
如果您使用@try ... @catch
,不会导致您的应用崩溃。
我无法想象为什么这不是默认值。
答案 2 :(得分:3)
答案 3 :(得分:2)
答案 4 :(得分:1)
我正在努力理解这一点:为什么NSApplication上的以下类别方法会导致无限循环?在那个无限循环中,“未被捕获的异常被引发”被无限次地注销:
- (void)reportException:(NSException *)anException
{
// handle the exception properly
(*NSGetUncaughtExceptionHandler())(anException);
}
为了测试(和理解目的),这是我唯一做的事情,即只创建上面的类别方法。 (根据http://www.cocoadev.com/index.pl?StackTraces)
中的说明为什么会导致无限循环?它与默认的未捕获异常处理程序方法应该做的不一致,即只记录异常并退出程序。 (见http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Exceptions/Concepts/UncaughtExceptions.html#//apple_ref/doc/uid/20000056-BAJDDGGD)
可能是默认的未捕获异常处理程序实际上再次抛出异常,导致这个无限循环?
注意:我知道仅创建此类别方法很愚蠢。这样做的目的是为了更好地理解。
更新:没关系,我想我现在得到了这个。这是我的看法。默认情况下,正如我们所知,NSApplication的 reportException:方法记录异常。但是,根据文档,默认的未捕获异常处理程序会记录异常并存在程序。但是,在文档中这应该更加精确:默认的未捕获异常处理程序调用NSApplication的reportException:方法(为了记录它,方法的默认实现确实如此),然后存在程序即可。所以现在应该清楚为什么在重写的reportException中调用默认的未捕获异常处理程序:导致无限循环:前者调用后者。
答案 5 :(得分:1)
因此,事实证明,似乎没有在应用程序委托方法中调用异常处理程序的原因是_NSAppleEventManagerGenericHandler
(私有API)具有@try
@catch
捕获所有异常并在调用errAEEventNotHandled
OSErr返回之前仅对它们调用NSLog的代码块。这意味着您不仅会错过应用程序启动过程中的任何异常,而且还会错过处理AppleEvent期间发生的所有异常,包括(但不限于)打开文档,打印,退出和任何AppleScript。
因此,我对此的“解决方案”:
#import <Foundation/Foundation.h>
#include <objc/runtime.h>
@interface NSAppleEventManager (GTMExceptionHandler)
@end
@implementation NSAppleEventManager (GTMExceptionHandler)
+ (void)load {
// Magic Keyword for turning on crashes on Exceptions
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];
// Default AppleEventManager wraps all AppleEvent calls in a @try/@catch
// block and just logs the exception. We replace the caller with a version
// that calls through to the NSUncaughtExceptionHandler if set.
NSAppleEventManager *mgr = [NSAppleEventManager sharedAppleEventManager];
Class class = [mgr class];
Method originalMethod = class_getInstanceMethod(class, @selector(dispatchRawAppleEvent:withRawReply:handlerRefCon:));
Method swizzledMethod = class_getInstanceMethod(class, @selector(gtm_dispatchRawAppleEvent:withRawReply:handlerRefCon:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (OSErr)gtm_dispatchRawAppleEvent:(const AppleEvent *)theAppleEvent
withRawReply:(AppleEvent *)theReply
handlerRefCon:(SRefCon)handlerRefCon {
OSErr err;
@try {
err = [self gtm_dispatchRawAppleEvent:theAppleEvent withRawReply:theReply handlerRefCon:handlerRefCon];
} @catch(NSException *exception) {
NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
if (handler) {
handler(exception);
}
@throw;
}
@catch(...) {
@throw;
}
return err;
}
@end
有趣的附加注释:NSLog(@"%@", exception)
等效于NSLog(@"%@", exception.reason)
。 NSLog(@"%@", [exception debugDescription])
将为您提供原因以及完全符号化的堆栈回溯。
_NSAppleEventManagerGenericHandler
中的默认版本仅调用NSLog(@"%@", exception)
(macOS 10.14.4(18E226))