Domanda

Il problema

Sto scrivendo un'applicazione Cocoa e desidero sollevare eccezioni che causeranno l'arresto anomalo dell'applicazione rumorosamente.

Ho le seguenti righe nel mio delegato dell'applicazione:

[NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
abort();

Il problema è che non bloccano l'applicazione: il messaggio viene semplicemente registrato sulla console e l'app continua a funzionare alla grande.

A quanto ho capito, il punto centrale delle eccezioni è che vengono licenziate in circostanze eccezionali.In queste circostanze, voglio che l'applicazione si chiuda in modo ovvio.E questo non accade.

Quello che ho provato

Ho provato:

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorOnMainThread:@selector(crash) withObject:nil waitUntilDone:YES];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

che non funziona e

-(void)applicationDidFinishLaunching:(NSNotification *)note
    // ...
    [self performSelectorInBackground:@selector(crash) withObject:nil];
}

-(void)crash {
    [NSException raise:NSInternalInconsistencyException format:@"This should crash the application."];
    abort();
}

che, in modo piuttosto confuso, funziona come previsto.

Cosa sta succedendo?Che cosa sto facendo di sbagliato?

È stato utile?

Soluzione

AGGIORNAMENTO - 16 novembre 2010: Ci sono alcuni problemi con questa risposta quando vengono generate eccezioni all'interno dei metodi IBAction.Vedi invece questa risposta:

Come posso impedire a HIToolbox di rilevare le mie eccezioni?


Questo si espande Quello di David Gelhar risposta e il collegamento da lui fornito.Di seguito è riportato come l'ho fatto sovrascrivendo NSApplication -reportException: metodo.Innanzitutto, crea una categoria ExceptionHandling per NSApplication (per tua informazione, dovresti aggiungere un acronimo di 2-3 lettere prima di "ExceptionHandling" per ridurre il rischio di conflitti di nomi):

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

In secondo luogo, all'interno del delegato di NSApplication, ho fatto quanto segue:

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

Piuttosto che usare NSApp terminate:, Puoi chiamare exit() Invece. terminate: è più Cacao-kosher, anche se potresti voler saltare il tuo applicationShouldTerminate: codice nel caso in cui venisse generata un'eccezione e si verificasse semplicemente un crash exit():

#import "sysexits.h"

// ...

exit(EX_SOFTWARE);

Ogni volta che viene generata un'eccezione, sul file filo principale, e non viene rilevato e distrutto, verrà ora chiamato il gestore delle eccezioni non rilevate personalizzato anziché quello di NSApplication.Ciò ti consente, tra le altre cose, di mandare in crash la tua applicazione.


AGGIORNAMENTO:

Sembra che ci sia un piccolo problema tecnico nel codice sopra.Il tuo gestore di eccezioni personalizzato non "si attiverà" e funzionerà fino a quando NSApplication non avrà finito di chiamare tutti i suoi metodi delegati.Ciò significa che se esegui del codice di configurazione all'interno applicazioneWillFinishLaunching: O applicationDidFinishLaunching: O sveglioDaNib:, il gestore delle eccezioni NSApplication predefinito sembra essere in-play fino a quando non viene completamente inizializzato.

Ciò significa che se lo fai:

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
        NSSetUncaughtExceptionHandler(&exceptionHandler);

        MyClass *myClass = [[MyClass alloc] init];   // throws an exception during init...
}

Tuo eccezioneHandler non otterrà l'eccezione.NSApplication lo farà e lo registrerà semplicemente.

Per risolvere questo problema, inserisci semplicemente qualsiasi codice di inizializzazione all'interno di a @try/@catch/@finally blocca e puoi chiamare il tuo personalizzato eccezioneHandler:

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

Adesso tuo exceptionHandler() ottiene l'eccezione e può gestirla di conseguenza.Dopo che NSApplication ha terminato di chiamare tutti i metodi delegati, il file NSApplication+ExceptionHandling.h La categoria entra in gioco, chiamandoExceptionHandler() tramite la sua consuetudine -reportException: metodo.A questo punto non devi preoccuparti di @try/@catch/@finally quando vuoi che le eccezioni vengano sollevate al tuo Uncaught Exception Handler.

Sono un po' sconcertato da ciò che sta causando questo.Probabilmente qualcosa dietro le quinte nell'API.Si verifica anche quando sottoclasso NSApplication, anziché aggiungere una categoria.Potrebbero esserci anche altri avvertimenti allegati a questo.

Altri suggerimenti

Si rivela una soluzione molto semplice:

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

IT non Ash> L'app se si utilizza @try ... @catch.

Non posso iniziare a immaginare perché non è il valore predefinito.

Forse è possibile utilizzare NsSetuncheDexceptionHandler o creare una categoria su NSApplication che sostituisce - ReportException: , come suggerito a http://www.cocoadev.com/index.pl?stackraces

Ho pubblicato questa domanda e rispondi come vorrei che qualcuno mi avesse detto questo, oh, circa un anno fa:

Le eccezioni gettate sul filo principale sono catturate da NSApplication.

Skim Leggi i documenti su NSexception End to End, senza menzione di questo che posso ricordare.L'unica ragione per cui so che questo è a causa del fantastico cacao dev:

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

La soluzione.Immagino.

Ho un daemon senza interfaccia utente che funziona quasi interamente sul filo principale.Dovrò trasferire l'intera app per eseguire fili di sfondo, a meno che qualcun altro possa suggerire un modo per fermare NSApplication catturare solo le eccezioni che lanciano.Sono abbastanza sicuro che non è possibile.

Sto cercando di capirlo correttamente: perché il metodo di categoria seguente su NSApplication porta a un ciclo infinito? In quel ciclo infinito, "è stata sollevata un'eccezione non rilevata" "è disconnesso infinitamente molte volte:

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

Per i test (e la comprensione dei fini), questa è l'unica cosa che faccio, cioè crea il metodo di cui sopra. (Secondo le istruzioni in http://www.cocoadev.com/index.pl?stacktraces)

Perché dovrebbe causare un ciclo infinito? Non è coerente con ciò che il metodo del gestore di eccezione non rilevato è necessario fare, I.e. Basta registrare l'eccezione e uscire dal programma. (Vedi http://developer.apple.com/mac/library/documentation/cocoa/conceptual/exceptions/concepts/unciaughthexcetions.html#//apple_ref/doc/uid/20000056-bajdddggd ) Potrebbe essere che il gestore di eccezione non rilevato predefinito stia effettivamente lanciando di nuovo l'eccezione, portando a questo anello infinito?

Nota: so che è sciocco creare solo questo metodo di categoria. Lo scopo di questo è quello di comprendere meglio.

Aggiornamento: non importa, penso di averlo ora. Ecco la mia presa. Per impostazione predefinita, come lo conosciamo, la ReportExceptionException: il metodo registra l'eccezione . Ma, secondo i documenti, il gestore di eccezione non rilevato non registra l'eccezione ed esiste il programma. Tuttavia, questo dovrebbe essere formulato in questo modo nei documenti per essere più precisi: il gestore di eccezione non rilevato predefinito chiama il reportException del report di NSApplication: metodo (per registrarlo, che l'implementazione predefinita del metodo fa anzi), e quindi esiste il programma . Allora ora Dovrebbe essere chiaro perché chiamare il gestore di eccezione non rilevante predefinito all'interno di un report report sovrascritto: provoca un ciclo infinito: i precedenti chiama quest'ultimo .

Quindi si disattiva il motivo che sembra che il gestore di eccezione non venga chiamato nei metodi Delegati dell'applicazione è che _NSAppleEventManagerGenericHandler (un'API privata) ha un blocco generacodicitagCodeGode di @try che sta catturando tutte le eccezioni e semplicemente chiamando NSLOG su di loroprima di tornare con un @catch Oserr.Ciò significa che non solo permetterete delle eccezioni nell'avvio dell'app, ma essenzialmente tutte le eccezioni che si verificano all'interno della gestione di un AppleEvent che include (ma non è limitato a) Apertura di documenti, stampa, smettere e qualsiasi applescript. Allora, la mia "Fix" per questo:

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

Divertente Nota extra: errAEEventNotHandled è equivalente a NSLog(@"%@", exception).NSLog(@"%@", exception.reason) ti darà la ragione più il backtrace dello stack simboleggiato.

La versione predefinita nel NSLog(@"%@", [exception debugDescription]) chiama solo _NSAppleEventManagerGenericHandler (MacOS 10.14.4 (18E226))

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top