时间:2010-07-26 15:22:13

标签: cocoa exception multithreading crash raise

6 个答案:

答案 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))