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

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

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


这扩展了David Gelhar 的回答以及他提供的链接。下面是我如何通过覆盖 NSApplication 的-reportException:方法来做到这一点。首先,为 NSApplication 创建一个 ExceptionHandling 类别(仅供参考,您应该在“ExceptionHandling”之前添加一个 2-3 个字母的首字母缩写词,以减少名称冲突的风险):

NSApplication+异常处理.h

#import <Cocoa/Cocoa.h>

@interface NSApplication (ExceptionHandling)

- (void)reportException:(NSException *)anException;

@end

NSApplication+异常处理.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/@finally。

我对造成这种情况的原因感到有些困惑。可能是 API 的幕后工作。即使我将 NSApplication 子类化,而不是添加一个类别,它也会发生。可能还有其他警告。

于 2010-08-05T20:54:18.200 回答
8

原来有一个非常简单的解决方案:

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

如果您使用@try ... @catch.

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

于 2016-09-23T03:49:20.427 回答
3

也许您可以使用NSSetUncaughtExceptionHandler,或者在 NSApplication 上创建一个覆盖-reportException:的类别,如http://www.cocoadev.com/index.pl?StackTraces所建议的那样

于 2010-07-26T15:51:48.760 回答
2

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

在主线程上抛出的异常被 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 回答
1

所以事实证明,在您的应用程序委托方法中似乎没有调用异常处理程序的原因是_NSAppleEventManagerGenericHandler(一个私有 API)有一个块来捕获所有异常,并在返回OSErr@try @catch之前对它们调用 NSLog 。errAEEventNotHandled这意味着您不仅会错过应用程序启动中的任何异常,而且基本上会错过处理 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))

于 2019-05-17T22:55:36.497 回答