11

問題

私はCocoaアプリケーションを書いていますが、アプリケーションを騒々しくクラッシュさせる例外を発生させたいと思っています。

アプリケーションデリゲートに次の行があります。

[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();

問題は、アプリケーションがダウンしないことです。メッセージはコンソールに記録されるだけで、アプリはそれを楽しい方法で実行します。

私が理解しているように、例外の全体的なポイントは、例外的な状況下で解雇されるということです。このような状況では、アプリケーションを明白な方法で終了させたいと思います。そして、これは起こりません。

私が試したこと

私はもう試した:

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorOnMainThread:@selector(crash) withObject:nil waitUntilDone:YES];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

これは機能せず、

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorInBackground:@selector(crash) withObject:nil];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

これは、かなり紛らわしいことに、期待どおりに機能します。

どうしたの?私は何が間違っているのですか?

4

6 に答える 6

10

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.


UPDATE:

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.

于 2010-08-05T20:54:18.200 に答える
8

非常に単純な解決策があることがわかりました。

[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }];

を使用すると、アプリがクラッシュしませ@try ... @catch

これがデフォルトではない理由を想像し始めることはできません。

于 2016-09-23T03:49:20.427 に答える
3

http://www.cocoadev.com/index.pl?StackTracesで提案されているように、NSSetUncaughtExceptionHandlerを使用するか、 -reportException:をオーバーライドする NSApplication でカテゴリを作成することができます。

于 2010-07-26T15:51:48.760 に答える
2

約1年前に誰かが私にこれを言ってくれたらよかったのにと思って、私はこの質問と回答を投稿しました:

メイン スレッドでスローされた例外は、NSApplication によってキャッチされます。

私は NSException のドキュメントを端から端までざっと読んでいますが、これについては言及していません。私がこれを知っている唯一の理由は、素晴らしい Cocoa Dev のおかげです。

http://www.cocoadev.com/index.pl?ExceptionHandling

ソリューション。私は推測する。

メイン スレッドでほぼ完全に実行される UI のないデーモンがあります。スローした例外だけをキャッチする NSApplication を停止する方法を他の誰かが提案できない限り、アプリ全体を転送してバックグラウンド スレッドを実行する必要があります。それは不可能だと確信しています。

于 2010-07-26T15:30:02.780 に答える
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: 内でデフォルトのキャッチされていない例外ハンドラーを呼び出すと、無限ループが発生する理由が明らかになったはずです前者が後者を呼び出します

于 2011-06-02T10:50:00.267 に答える