代码之家  ›  专栏  ›  技术社区  ›  John Gallagher

为什么提出一个NSException不能关闭我的应用程序?

  •  10
  • John Gallagher  · 技术社区  · 14 年前

    问题

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

    令人困惑的是,它的工作原理和预期的一样。

    5 回复  |  直到 14 年前
        1
  •  10
  •   Community CDub    7 年前

    更新-2010年11月16日:

    How can I stop HIToolbox from catching my exceptions?


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

    #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...
    }
    

    而不是使用NSApp的 terminate: exit() 终止: applicationShouldTerminate: 退出()

    #import "sysexits.h"
    
    // ...
    
    exit(EX_SOFTWARE);
    

    每当抛出异常时 主螺纹 ,并且它不会被捕获和销毁,您的自定义未捕获异常处理程序现在将被调用,而不是NSApplication的。这允许您使应用程序崩溃。


    更新:

    上面的代码中似乎有一个小故障。在NSApplication完成调用其所有委托方法之前,您的自定义异常处理程序不会“启动”并工作。这意味着如果您在 应用程序完成启动: 唤醒Fromnib:

    这意味着如果你这样做:

    - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
    {
            NSSetUncaughtExceptionHandler(&exceptionHandler);
    
            MyClass *myClass = [[MyClass alloc] init];   // throws an exception during init...
    }
    

    你的 异常处理程序 不会有例外。NSApplication会,它只会记录它。

    @try/@catch/@finally :

    - (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调用完所有委托方法之后 应用程序+异常处理.h 类别启动,通过其自定义 方法。此时,当您希望异常引发到未捕获的异常处理程序时,您不必担心@try/@catch/@finally。

    我对造成这一切的原因有点困惑。可能是API中的一些幕后操作。即使我将NSApplication子类化,而不是添加一个类别,也会发生这种情况。可能还有其他的注意事项。

        2
  •  8
  •   George    8 年前

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

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

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

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

        3
  •  3
  •   David Gelhar    14 年前
        4
  •  2
  •   John Gallagher    14 年前

    我发布了这个问题和答案,希望有人在一年前告诉我:

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

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

    解决方案。我想是吧。

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

        5
  •  1
  •   Enchilada    13 年前

    我正试图正确地理解这一点:为什么NSApplication上的category方法会导致无限循环?在该无限循环中,“引发了未捕获的异常”被无限次注销:

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

    为了测试(和理解的目的),这是我唯一要做的事情,即创建上面的category方法(根据中的说明 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 )

    会不会是默认的未捕获异常处理程序再次抛出异常,从而导致这个无限循环?

    注意:我知道只创建这个category方法很愚蠢。这样做的目的是为了更好地理解。

    reportException:方法记录异常 默认的未捕获异常处理程序调用NSApplication的reportException:method(为了记录它,方法的默认实现确实做到了这一点),然后存在于程序中 . 所以呢 现在 应该很清楚为什么在重写的reportException:中调用默认的未捕获异常处理程序会导致无限循环: .

        6
  •  1
  •   dmaclach    5 年前

    因此,在应用程序委托方法中似乎没有调用异常处理程序的原因是 _NSAppleEventManagerGenericHandler @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]) 将为您提供原因以及完全符号化的堆栈回溯。

    中的默认版本 _NSAPPLEEVENTMANAGERENERICHANDLER公司 只是打电话而已 (macOS 10.14.4(18E226))

    推荐文章