Почему повышение NSException не приводит к закрытию моего приложения? - PullRequest
10 голосов
/ 26 июля 2010

Проблема

Я пишу приложение Какао и хочу вызвать исключения, которые будут вызывать сбой приложения.

В моем делегате приложения есть следующие строки:

[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();
}

, что довольно запутанно, работает как положено.

Что происходит? Что я делаю не так?

Ответы [ 6 ]

10 голосов
/ 06 августа 2010

ОБНОВЛЕНИЕ - 16 ноября 2010 г .: Есть некоторые проблемы с этим ответом, когда в методах IBAction возникают исключения. Смотрите этот ответ вместо:

Как я могу помешать HIToolbox перехватывать мои исключения?


Это расширяет ответ Дэвида Гелхара и ссылку, которую он предоставил. Ниже показано, как я это сделал, переопределив метод -reportException: NSApplication. Во-первых, создайте категорию ExceptionHandling для NSApplication (к вашему сведению, вы должны добавить 2-3-буквенную аббревиатуру перед «ExceptionHandling», чтобы уменьшить риск конфликта имен):

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: является более какао-кошерным, хотя вы можете пропустить свой код applicationShouldTerminate: в случае, если возникло исключение, и просто аварийно завершить работу с exit():

#import "sysexits.h"

// ...

exit(EX_SOFTWARE);

Всякий раз, когда выдается исключение, в главном потоке , и оно не перехватывается и не уничтожается, ваш собственный обработчик необработанных исключений теперь будет вызываться вместо NSApplication. Это позволяет, помимо прочего, аварийно завершить работу приложения.


UPDATE:

В приведенном выше коде, похоже, есть небольшая ошибка. Ваш пользовательский обработчик исключений не будет включаться и работать до тех пор, пока 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 вступает в действие, вызывая exceptionHandler () через свой собственный метод -reportException:. На этом этапе вам не нужно беспокоиться о @ try / @ catch / @ finally, когда вы хотите, чтобы исключения обрабатывались вашим обработчиком исключений Uncaught.

Я немного сбит с толку тем, что вызывает это. Возможно, что-то закулисное в API. Это происходит даже тогда, когда я делаю подкласс NSApplication, а не добавляю категорию. К этому также могут быть приложены другие оговорки.

6 голосов
/ 23 сентября 2016

Оказывается, очень простое решение:

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

Это действительно не сбой вашего приложения, если вы используете @try ... @catch.

Я не могу представить, почему это не по умолчанию.

3 голосов
/ 26 июля 2010

Может быть, вы можете использовать NSSetUncaughtExceptionHandler или создать категорию для NSApplication, которая переопределяет -reportException: , как предложено в http://www.cocoadev.com/index.pl?StackTraces

2 голосов
/ 26 июля 2010

Я разместил этот вопрос и ответ, как я хотел бы, чтобы кто-то сказал мне это, о, год назад:

Исключения, выданные в основном потоке, перехватываются NSApplication.

Я прочитал документы по NSException от начала до конца, не упоминая об этом, что могу вспомнить.Единственная причина, по которой я это знаю, из-за фантастического разработчика Cocoa:

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

Решение.Я думаю.

У меня есть демон без пользовательского интерфейса, который почти полностью работает в основном потоке.Мне придется перенести все приложение для запуска фоновых потоков, если кто-то еще не может предложить способ остановить NSApplication, перехватывая только те исключения, которые я выбрасываю.Я почти уверен, что это невозможно.

1 голос
/ 18 мая 2019

Таким образом, получается, что причина того, что обработчик исключений не вызывается в методах делегатов приложения, заключается в том, что _NSAppleEventManagerGenericHandler (частный API) имеет блок @try @catch, который перехватывает все исключения и просто вызываю NSLog для них перед возвратом с errAEEventNotHandled OSErr. Это означает, что вы не только пропустите какие-либо исключения при запуске приложения, но, по сути, любые исключения, возникающие при обработке 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))

1 голос
/ 02 июня 2011

Я пытаюсь понять это правильно: почему следующий метод категории в 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)

Может ли быть так, что обработчик необработанных исключений по умолчанию на самом деле снова генерирует исключение, приводя к этому бесконечному циклу?

Примечание: я знаю, что глупо создавать только этот метод категории.Цель этого состоит в том, чтобы получить лучшее понимание.

ОБНОВЛЕНИЕ: не берите в голову, я думаю, что я получаю это сейчас. Вот мой взгляд. По умолчанию, как мы знаем, reportException NSApplication: метод регистрирует исключение. Но, согласно документам, обработчик необработанных исключений по умолчанию регистрирует исключение и создает программу, однако в документах это следует сформулировать следующим образом: Обработчик необработанных исключений по умолчанию вызывает NSApplication'sreportException: метод (для регистрации это, который метод по умолчанию яmplementation действительно делает), а затем существует программа .Так что сейчас должно быть понятно, почему вызов обработчика неперехваченного исключения по умолчанию внутри переопределенного reportException: вызывает бесконечный цикл: Первый вызывает последний .

...