为什么引发 NSException 不会导致我的应用程序崩溃?

发布于 2024-09-11 23:55:06 字数 1086 浏览 6 评论 0原文

问题

我正在编写一个 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();
}

哪个按预期工作,相当令人困惑。

这是怎么回事?我做错了什么?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

我不咬妳我踢妳 2024-09-18 23:55:06

更新 - 2010 年 11 月 16 日: 当 IBAction 方法内引发异常时,此答案存在一些问题。请参阅此答案:

How can I stop HIToolbox捕获我的异常?


这扩展了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...
}

您可以调用 exit( 而不是使用 NSApp 的 terminate: ) 来代替。 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/@finally。

我对造成这种情况的原因有点困惑。可能是 API 中幕后的东西。即使当我子类化 NSApplication,而不是添加类别时,也会发生这种情况。可能还有其他警告。

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.

冷心人i 2024-09-18 23:55:06

事实证明,有一个非常简单的解决方案:

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

如果满足以下条件,它不会使您的应用程序崩溃您使用@try ... @catch

我无法想象为什么这不是默认设置。

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.

煞人兵器 2024-09-18 23:55:06

也许你可以使用 NSSetUncaughtExceptionHandler,或者在 NSApplication 上创建一个覆盖 -reportException:,如 http://www.cocoadev.com/index.pl?StackTraces

Maybe you can use NSSetUncaughtExceptionHandler, or create a category on NSApplication that overrides -reportException:, as suggested at http://www.cocoadev.com/index.pl?StackTraces

大海や 2024-09-18 23:55:06

我发布了这个问题和答案,因为我希望有人告诉我这一点,哦,大约一年前:

主线程上抛出的异常被 NSApplication 捕获。

我浏览了一下 NSException 端的文档到最后,我记得没有提到这一点。我知道这一点的唯一原因是因为出色的 Cocoa Dev:

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

解决方案。我猜。

我有一个没有 UI 的守护进程,几乎完全在主线程上运行。我将不得不转移整个应用程序来运行后台线程,除非其他人可以建议一种方法来阻止 NSApplication 仅捕获我抛出的异常。我很确定那是不可能的。

I've posted this question and answer as I wish someone had told me this, oh, about a year ago:

Exceptions thrown on the main thread are caught by NSApplication.

I skim read the docs on NSException end to end, with no mention of this that I can recall. The only reason I know this is because of the fantastic Cocoa Dev:

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

The Solution. I guess.

I've got a daemon with no UI that almost entirely runs on the main thread. I'll have to transfer the whole app to run background threads, unless someone else can suggest a way of stopping NSApplication catching just the exceptions I throw. I'm pretty sure that's not possible.

星星的軌跡 2024-09-18 23:55:06

我试图正确理解这一点:为什么 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:中调用默认的未捕获异常处理程序会导致无限循环:前者调用后者

I'm trying to understand this properly: Why does the following category method on NSApplication lead to an infinite loop? In that infinite loop, "An uncaught exception was raised" is logged out infinitely many times:

- (void)reportException:(NSException *)anException
{
    // handle the exception properly
    (*NSGetUncaughtExceptionHandler())(anException);
}

For testing (and understanding purposes), this is the only thing I do, i.e. just create the above category method. (According to the instructions in http://www.cocoadev.com/index.pl?StackTraces)

Why would this cause an infinite loop? It's not consistent with what the default uncaught exception handler method should do, i.e. just log the exception and exit the program. (See http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Exceptions/Concepts/UncaughtExceptions.html#//apple_ref/doc/uid/20000056-BAJDDGGD)

Could it be that the default uncaught exception handler is actually throwing the exception again, leading to this infinite loop?

Note: I know it's silly to create only this category method. The purpose of this is to gain a better understanding.

UPDATE: Never mind, I think i get this now. Here is my take. By default, as we know, NSApplication's reportException: method logs the exception. But, according to the docs, the default uncaught exception handler logs the exception and exists the program. However, this should be worded like this in the docs to be more precise: The default uncaught exception handler calls NSApplication's reportException: method (in order to log it, which the method's default implementation indeed does), and then exists the program. So now it should be clear why calling the default uncaught exception handler inside an overridden reportException: causes an infinite loop: The former calls the latter.

情徒 2024-09-18 23:55:06

因此,在应用程序委托方法中未调用异常处理程序的原因是 _NSAppleEventManagerGenericHandler (私有 API)具有 @try < code>@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))

So it turns out the reason that it appears that the exception handler doesn't get called in your application delegate methods is that _NSAppleEventManagerGenericHandler (a private API) has a @try @catch block that is catching all exceptions and just calling NSLog on them before returning with a errAEEventNotHandled OSErr. This means that not only are you going to miss any exceptions in app start up, but essentially any exceptions that occur inside of handling an AppleEvent which includes (but is not limited to) opening documents, printing, quitting and any AppleScript.

So, my "fix" for this:

#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

Fun extra note: NSLog(@"%@", exception) is equivalent to NSLog(@"%@", exception.reason). NSLog(@"%@", [exception debugDescription]) will give you the reason plus the fully symbolicated stack backtrace.

The default version in the _NSAppleEventManagerGenericHandler just calls NSLog(@"%@", exception) (macOS 10.14.4 (18E226))

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文