The Problem
I'm writing a Cocoa application and I want to raise exceptions that will crash the application noisily.
I have the following lines in my application delegate:
[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();
The problem is, they don't bring down the application - the message is just logged to the console and the app carries on it's merry way.
As I understand it, the whole point of exceptions is that they're fired under exceptional circumstances. In these circumstances, I want the application to quit in an obvious way. And this doesn't happen.
What I've tried
I've tried:
-(void)applicationDidFinishLaunching:(NSNotification *)note
// ...
[self performSelectorOnMainThread:@selector(crash) withObject:nil waitUntilDone:YES];
}
-(void)crash {
[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();
}
which doesn't work and
-(void)applicationDidFinishLaunching:(NSNotification *)note
// ...
[self performSelectorInBackground:@selector(crash) withObject:nil];
}
-(void)crash {
[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();
}
which, rather confusingly, works as expected.
What's going on? What am I doing wrong?
UPDATE - Nov 16, 2010: There are some issues with this answer when exceptions are thrown inside IBAction methods. See this answer instead:
How can I stop HIToolbox from catching my exceptions?
This expands on David Gelhar's answer, and the link he provided. Below is how I did it by overriding NSApplication's -reportException:
method. First, create an ExceptionHandling Category for NSApplication (FYI, you should add a 2-3 letter acronym before "ExceptionHandling" to reduce the risk of name clashing):
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
Second, inside NSApplication's delegate, I did the following:
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...
}
Rather than use NSApp's terminate:
, you can call exit()
instead. terminate:
is more Cocoa-kosher, though you may want to skip your applicationShouldTerminate:
code in the event an exception was thrown and simply hard-crash with exit()
:
#import "sysexits.h"
// ...
exit(EX_SOFTWARE);
Whenever an exception is thrown, on the main thread, and it's not caught and destroyed, your custom uncaught exception handler will now be called instead of NSApplication's. This allows you to crash your application, among other things.
There appears to be a small glitch in the above code. Your custom exception handler won't "kick in" and work until after NSApplication has finished calling all of its delegate methods. This means that if you do some setup-code inside applicationWillFinishLaunching: or applicationDidFinishLaunching: or awakeFromNib:, the default NSApplication exception handler appears to be in-play until after it's fully initialized.
What that means is if you do this:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSSetUncaughtExceptionHandler(&exceptionHandler);
MyClass *myClass = [[MyClass alloc] init]; // throws an exception during init...
}
Your exceptionHandler won't get the exception. NSApplication will, and it'll just log it.
To fix this, simply put any initialization code inside a @try/@catch/@finally
block and you can call your custom 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...
}
}
Now your exceptionHandler()
gets the exception and can handle it accordingly. After NSApplication has finished calling all delegate methods, the NSApplication+ExceptionHandling.h Category kicks in, calling exceptionHandler() through its custom -reportException:
method. At this point you don't have to worry about @try/@catch/@finally when you want exceptions to raise to your Uncaught Exception Handler.
I'm a little baffled by what is causing this. Probably something behind-the-scenes in the API. It occurs even when I subclass NSApplication, rather than adding a category. There may be other caveats attached to this as well.
There turns out to be a very simple solution:
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];
It does not crash your app if you use @try ... @catch
.
I can't begin to imagine why this isn't the default.
Maybe you can use NSSetUncaughtExceptionHandler, or create a category on NSApplication that overrides -reportException:, as suggested at http://www.cocoadev.com/index.pl?StackTraces
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With