Obiettivo-C in breve eccezioni ed errori

In Objective-C, ci sono due tipi di errori che possono verificarsi mentre un programma è in esecuzione. inaspettato gli errori sono errori di programmazione "gravi" che in genere causano l'uscita prematura del programma. Questi sono chiamati eccezioni, dal momento che rappresentano una condizione eccezionale nel tuo programma. D'altro canto, previsto gli errori si verificano naturalmente nel corso dell'esecuzione di un programma e possono essere utilizzati per determinare il successo di un'operazione. Questi sono indicati come errori.

Puoi anche avvicinarti alla distinzione tra eccezioni ed errori come differenza nei loro target di riferimento. In generale, vengono utilizzate eccezioni per informare il programmatore su qualcosa che è andato storto, mentre gli errori sono usati per informare il utente che un'azione richiesta non può essere completata.

Controllo del flusso per eccezioni ed errori

Ad esempio, provare ad accedere a un indice di array che non esiste è un'eccezione (un errore del programmatore), mentre non riuscire ad aprire un file è un errore (un errore dell'utente). Nel primo caso, probabilmente qualcosa è andato storto nel flusso del programma e probabilmente dovrebbe spegnersi subito dopo l'eccezione. In quest'ultimo caso, si vorrebbe dire all'utente che il file non può essere aperto e se possibile chiedere di riprovare l'azione, ma non c'è motivo per cui il programma non sia in grado di continuare a funzionare dopo l'errore.


La gestione delle eccezioni

Il vantaggio principale delle capacità di gestione delle eccezioni di Objective-C è la capacità di separare la gestione degli errori dal rilevamento degli errori. Quando una porzione di codice incontra un'eccezione, può "lanciarla" sul blocco di gestione degli errori più vicino, che può "catturare" eccezioni specifiche e gestirle in modo appropriato. Il fatto che le eccezioni possano essere generate da posizioni arbitrarie elimina la necessità di controllare costantemente i messaggi di successo o di errore da ciascuna funzione coinvolta in una particolare attività.

Il @provare, @catturare(), e @finalmente le direttive del compilatore sono usate per catturare e gestire le eccezioni, e il @gettare la direttiva viene utilizzata per rilevarli. Se hai lavorato con le eccezioni in C #, questi costrutti di gestione delle eccezioni dovrebbero esserti familiari.

È importante notare che in Objective-C le eccezioni sono relativamente lente. Di conseguenza, il loro uso dovrebbe essere limitato alla rilevazione di errori di programmazione gravi, non per il flusso di controllo di base. Se stai cercando di determinare cosa fare in base a un previsto errore (ad esempio, non riuscendo a caricare un file), fare riferimento al Sezione Gestione degli errori.

La classe NSException

Le eccezioni sono rappresentate come istanze del NSException classe o una sua sottoclasse. Questo è un modo conveniente per incapsulare tutte le informazioni necessarie associate a un'eccezione. Le tre proprietà che costituiscono un'eccezione sono descritte come segue:

  • nome - Un'istanza di NSString identifica in modo univoco l'eccezione.
  • ragionare - Un'istanza di NSString contenente una descrizione leggibile dall'uomo dell'eccezione.
  • userInfo - Un'istanza di NSDictionary che contiene informazioni specifiche dell'applicazione relative all'eccezione.

Il framework Foundation definisce diverse costanti che definiscono i nomi delle eccezioni "standard". Queste stringhe possono essere utilizzate per verificare quale tipo di eccezione è stata rilevata.

Puoi anche usare il initWithName: ragione: userInfo: metodo di inizializzazione per creare nuovi oggetti di eccezione con i propri valori. Oggetti di eccezione personalizzati possono essere catturati e lanciati usando gli stessi metodi trattati nelle prossime sezioni.

Generazione di eccezioni

Iniziamo dando un'occhiata al comportamento predefinito di gestione delle eccezioni di un programma. Il objectAtIndex: metodo di NSArray è definito per lanciare un NSRangeException (una sottoclasse di NSException) quando si tenta di accedere a un indice che non esiste. Quindi, se chiedi il 10esimo elemento di un array che ha solo tre elementi, avrai un'eccezione per sperimentare:

#importare  int main (int argc, const char * argv []) @autoreleasepool NSArray * crew = [array NSArrayWithObjects: @ "Dave", @ "Heywood", @ "Frank", nil]; // Questo genererà un'eccezione. NSLog (@ "% @", [crew objectAtIndex: 10]);  restituisce 0; 

Quando incontra un'eccezione non rilevata, Xcode interrompe il programma e indica la linea che ha causato il problema.

Interruzione di un programma a causa di un'eccezione non rilevata

Successivamente, impareremo come rilevare le eccezioni e impedire la chiusura del programma.

Cattura le eccezioni

Per gestire un'eccezione, qualsiasi codice quello potrebbe il risultato in un'eccezione dovrebbe essere inserito in a @provare bloccare. Quindi, è possibile rilevare eccezioni specifiche utilizzando il @catturare() direttiva. Se è necessario eseguire qualsiasi codice di pulizia, è possibile inserirlo a @finalmente bloccare. L'esempio seguente mostra tutte e tre queste direttive di gestione delle eccezioni:

@try NSLog (@ "% @", [crew objectAtIndex: 10]);  @catch (eccezione NSException *) NSLog (@ "Catturato un'eccezione"); // Ignoreremo silenziosamente l'eccezione.  @finally NSLog (@ "Cleaning up"); 

Questo dovrebbe produrre quanto segue nella tua console Xcode:

Preso un'eccezione! Nome: NSRangeException Motivo: *** - [__ NSArrayI objectAtIndex:]: indice 10 oltre i limiti [0 ... 2] Pulizia

Quando il programma incontra il [crew objectAtIndex: 10] messaggio, lancia un NSRangeException, che è catturato nel @catturare() direttiva. All'interno del @catturare() il blocco è dove viene effettivamente gestita l'eccezione. In questo caso, visualizziamo solo un messaggio di errore descrittivo, ma nella maggior parte dei casi, probabilmente vorrai scrivere del codice per occuparti del problema.

Quando si verifica un'eccezione nel @provare blocco, il programma salta al corrispondente @catturare() blocco, che significa qualsiasi codice dopo l'eccezione verificatasi non verrà eseguita. Questo pone un problema se il @provare il blocco ha bisogno di un po 'di pulizia (per esempio, se ha aperto un file, quel file deve essere chiuso). Il @finalmente il blocco risolve questo problema, dato che lo è garantito da eseguire indipendentemente dal fatto che si sia verificata un'eccezione. Questo lo rende il posto perfetto per legare qualsiasi estremità libera dal @provare bloccare.

Le parentesi dopo il @catturare() direttiva ti permette di definire quale tipo di eccezione stai cercando di catturare. In questo caso, è un NSException, che è la classe di eccezione standard. Ma un'eccezione può effettivamente essere qualunque classe: non solo un NSException. Ad esempio, il seguente @catturare() direttiva gestirà un oggetto generico:

@catch (id genericException)

Impareremo come lanciare istanze di NSException così come oggetti generici nella prossima sezione.

Eccezioni gettanti

Quando rilevi una condizione eccezionale nel tuo codice, crei un'istanza di NSException e popolarlo con le informazioni pertinenti. Quindi, lo lanci usando il nome appropriato @gettare direttiva, sollecitando il più vicino @provare/@catturare blocco per gestirlo.

Ad esempio, il seguente esempio definisce una funzione per la generazione di numeri casuali tra un intervallo specificato. Se il chiamante supera un intervallo non valido, la funzione genera un errore personalizzato.

#importare  int generateRandomInteger (int minimo, int massimo) if (minimo> = massimo) // Crea l'eccezione. NSException * exception = [NSException exceptionWithName: @ "RandomNumberIntervalException" motivo: @ "*** generateRandomInteger ():" "parametro massimo non maggiore del parametro minimo" userInfo: nil]; // Lancia l'eccezione. @throw exception;  // Restituisce un numero intero casuale. return arc4random_uniform ((maximum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try result = generateRandomInteger (0, 10);  @catch (eccezione NSException *) NSLog (@ "Problema !!! Caught exception:% @", [nome eccezione]);  NSLog (@ "Numero casuale:% i", risultato);  restituisce 0; 

Poiché questo codice passa un intervallo valido (0, 10) a generateRandomInteger (), non avrà un'eccezione da prendere. Tuttavia, se si modifica l'intervallo in qualcosa di simile (0, -10), vedrai il @catturare() blocco in azione. Questo è essenzialmente ciò che sta succedendo quando le classi del framework incontrano eccezioni (ad es NSRangeException cresciuto da NSArray).

È anche possibile rilanciare le eccezioni che hai già catturato. Questo è utile se vuoi essere informato che si è verificata una particolare eccezione, ma non necessariamente vuoi gestirla tu stesso. Per comodità, puoi anche omettere l'argomento di @gettare direttiva:

@try result = generateRandomInteger (0, -10);  @catch (eccezione NSException *) NSLog (@ "Problema !!! Caught exception:% @", [nome eccezione]); // Rilancia l'eccezione corrente. @gettare 

Ciò passa l'eccezione rilevata al gestore immediatamente successivo, che in questo caso è il gestore di eccezioni di livello superiore. Questo dovrebbe mostrare l'output del nostro @catturare() blocco, così come il default Termina l'app a causa dell'eccezione non rilevata ... messaggio, seguito da un'uscita improvvisa.

Il @gettare direttiva non è limitata a NSException oggetti: può letteralmente lanciare qualunque oggetto. L'esempio seguente lancia un NSNumber oggetto invece di un'eccezione normale. Osserva anche come puoi scegliere come target diversi oggetti aggiungendo multipli @catturare() dichiarazioni dopo il @provare bloccare:

#importare  int generateRandomInteger (int minimo, int massimo) if (minimo> = massimo) // Genera un numero utilizzando l'intervallo "predefinito". NSNumber * guess = [NSNumber numberWithInt: generateRandomInteger (0, 10)]; // Lancia il numero. @throw guess;  // Restituisce un numero intero casuale. return arc4random_uniform ((maximum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try result = generateRandomInteger (30, 10);  @catch (NSNumber * guess) NSLog (@ "Warning: Used default interval"); risultato = [indovina intValue];  @catch (eccezione NSException *) NSLog (@ "Problema !!! Caught exception:% @", [nome eccezione]);  NSLog (@ "Numero casuale:% i", risultato);  restituisce 0; 

Invece di lanciare un NSException oggetto, generateRandomInteger () prova a generare un nuovo numero tra alcuni limiti "predefiniti". L'esempio mostra come @gettare può funzionare con diversi tipi di oggetti, ma in senso stretto, questo non è il miglior design di applicazione, né è l'uso più efficiente degli strumenti di gestione delle eccezioni di Objective-C. Se stavi davvero pensando di usare il valore generato come fa il codice precedente, staresti meglio con un semplice controllo condizionale usando NSError, come discusso nella prossima sezione.

Inoltre, alcuni dei principali framework di Apple aspettarsi un NSException oggetto da lanciare, quindi fai attenzione agli oggetti personalizzati quando si integrano con le librerie standard.


Gestione degli errori

Mentre le eccezioni sono progettate per consentire ai programmatori di sapere quando le cose si sono fatalmente sbagliate, gli errori sono progettati per essere un modo efficiente e diretto per verificare se un'azione è riuscita o meno. A differenza delle eccezioni, errori siamo progettato per essere utilizzato nelle dichiarazioni di flusso di controllo giornaliere.

La classe NSError

L'unica cosa che gli errori e le eccezioni hanno in comune è che sono entrambi implementati come oggetti. Il NSError class incapsula tutte le informazioni necessarie per rappresentare gli errori:

  • codice - Un NSInteger che rappresenta l'identificativo univoco dell'errore.
  • dominio - Un'istanza di NSString definizione del dominio per l'errore (descritto in maggior dettaglio nella prossima sezione).
  • userInfo - Un'istanza di NSDictionary che contiene informazioni specifiche dell'applicazione correlate all'errore. Questo è tipicamente usato molto più del userInfo dizionario di NSException.

Oltre a questi attributi fondamentali, NSError memorizza anche diversi valori progettati per aiutare nel rendering e nell'elaborazione di errori. Tutti questi sono in realtà scorciatoie nel userInfo dizionario descritto nella lista precedente.

  • localizedDescription - Un NSString contenente la descrizione completa dell'errore, che in genere include il motivo dell'errore. Questo valore viene in genere visualizzato all'utente in un pannello di avviso.
  • localizedFailureReason - Un NSString contenente una descrizione autonoma del motivo dell'errore. Viene utilizzato solo dai client che desiderano isolare il motivo dell'errore dalla descrizione completa.
  • recoverySuggestion - Un NSString istruire l'utente su come recuperare dall'errore.
  • localizedRecoveryOptions - Un NSArray di titoli utilizzati per i pulsanti della finestra di dialogo di errore. Se questo array è vuoto, un singolo ok il pulsante viene visualizzato per ignorare l'avviso.
  • helpAnchor - Un NSString da visualizzare quando l'utente preme il tasto Aiuto pulsante di ancoraggio in un pannello di avviso.

Come con NSException, il initWithDomain: Codice: userInfo metodo può essere utilizzato per inizializzare personalizzato NSError casi.

Domini errore

Un dominio di errore è come un namespace per i codici di errore. I codici dovrebbero essere unici all'interno di un singolo dominio, ma possono sovrapporsi ai codici di altri domini. Oltre a prevenire le collisioni di codice, i domini forniscono anche informazioni su da dove proviene l'errore. I quattro principali domini di errori incorporati sono: NSMachErrorDomain, NSPOSIXErrorDomain, NSOSStatusErrorDomain, e NSCocoaErrorDomain. Il NSCocoaErrorDomain contiene i codici di errore per molti dei framework Objective-C standard di Apple; tuttavia, esistono alcuni framework che definiscono i propri domini (ad es., NSXMLParserErrorDomain).

Se devi creare codici di errore personalizzati per le tue librerie e applicazioni, devi sempre aggiungerli a il tuo dominio di errore: non estendere mai nessuno dei domini integrati. Creare il tuo dominio è un lavoro relativamente banale. Poiché i domini sono solo stringhe, tutto ciò che devi fare è definire una costante di stringa che non sia in conflitto con nessuno degli altri domini di errore nell'applicazione. Apple suggerisce che i domini assumano la forma di com...ErrorDomain.

Catturare errori

Non ci sono costrutti di linguaggio dedicati per la gestione NSError istanze (sebbene molte classi integrate siano progettate per gestirle). Sono progettati per essere utilizzati in combinazione con funzioni appositamente progettate che restituiscono un oggetto quando hanno successo e zero quando falliscono La procedura generale per catturare gli errori è la seguente:

  1. Dichiara un NSError variabile. Non è necessario allocarlo o inizializzarlo.
  2. Passa quella variabile come un doppio puntatore a una funzione che potrebbe causare un errore. Se qualcosa va storto, la funzione utilizzerà questo riferimento per registrare le informazioni sull'errore.
  3. Controllare il valore restituito di quella funzione in caso di esito positivo o negativo. Se l'operazione fallisce, puoi usare NSError per gestire da sé l'errore o visualizzarlo all'utente.

Come puoi vedere, una funzione in genere non lo è ritorno un NSError oggetto-restituisce qualsiasi valore si suppone se riesce, altrimenti restituisce zero. Dovresti sempre usare il valore di ritorno di una funzione per rilevare gli errori, mai usare la presenza o l'assenza di un NSError oggetto per verificare se un'azione è riuscita. Gli oggetti di errore dovrebbero solo descrivere un potenziale errore, non dirti se uno si è verificato.

L'esempio seguente dimostra un caso d'uso realistico per NSError. Usa un metodo di caricamento dei file di NSString, che è in realtà al di fuori dello scopo del libro. Il iOS in modo succinto il libro copre in modo approfondito la gestione dei file, ma per ora concentriamoci sulle funzionalità di gestione degli errori di Objective-C.

Innanzitutto, generiamo un percorso file che punta a ~ / Desktop / SomeContent.txt. Quindi, creiamo un NSError riferimento e passarlo al stringWithContentsOfFile: Codifica: errore: metodo per acquisire informazioni su eventuali errori che si verificano durante il caricamento del file. Si noti che stiamo passando a riferimento al *errore puntatore, il che significa che il metodo richiede un puntatore a un puntatore (cioè un puntatore doppio). Ciò consente al metodo di popolare la variabile con il proprio contenuto. Infine, controlliamo il valore di ritorno (non l'esistenza del errore variabile) per vedere se stringWithContentsOfFile: Codifica: errore: riuscito o no. Se lo facesse, è sicuro lavorare con il valore memorizzato nel file soddisfare variabile; altrimenti, usiamo il errore variabile per visualizzare informazioni su cosa è andato storto.

#importare  int main (int argc, const char * argv []) @autoreleasepool // Genera il percorso file desiderato. NSString * filename = @ "SomeContent.txt"; NSArray * paths = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); NSString * desktopDir = [percorsi objectAtIndex: 0]; NSString * path = [desktopDir stringByAppendingPathComponent: nome file]; // Prova a caricare il file. Errore NSError *; NSString * content = [NSString stringWithContentsOfFile: codifica del percorso: NSUTF8StringEncoding error: & error]; // Controlla se ha funzionato. if (content == nil) // Si è verificato un errore. NSLog (@ "Errore nel caricamento del file% @!", Percorso); NSLog (@ "Descrizione:% @", [error localizedDescription]); NSLog (@ "Motivo:% @", [errore localizedFailureReason]);  else // Contenuto caricato con successo. NSLog (@ "Contenuto caricato!"); NSLog (@ "% @", contenuto);  restituisce 0; 

Dal momento che il ~ / Desktop / SomeContent.txt il file probabilmente non esiste sulla tua macchina, questo codice causerà molto probabilmente un errore. Tutto ciò che devi fare per rendere il caricamento riuscito è creare SomeContent.txt sul tuo desktop.

Errori personalizzati

Gli errori personalizzati possono essere configurati accettando un doppio puntatore a un NSError oggetto e lo popola da solo. Ricorda che la tua funzione o metodo dovrebbe restituire un oggetto o zero, a seconda che abbia esito positivo o negativo (non restituire il NSError riferimento).

Nell'esempio seguente viene utilizzato un errore anziché un'eccezione per attenuare i parametri non validi nel file generateRandomInteger () funzione. Notare che **errore è un doppio puntatore, che ci consente di popolare la variabile sottostante all'interno della funzione. È molto importante verificare che l'utente abbia effettivamente passato un valido **errore parametro con se (errore! = NULL). Dovresti sempre farlo nelle tue stesse funzioni generatrici di errori. Dal momento che il **errore parametro è un doppio puntatore, possiamo assegnare un valore alla variabile sottostante tramite *errore. E ancora, controlliamo gli errori usando il valore di ritorno (se (risultato == nil)), non il errore variabile.

#importare  NSNumber * generateRandomInteger (int minimo, int massimo, errore NSError **) if (minimo> = massimo) if (errore! = NULL) // Crea l'errore. NSString * domain = @ "com.MyCompany.RandomProject.ErrorDomain"; int errorCode = 4; NSMutableDictionary * userInfo = [Dizionario NSMutableDictionary]; [userInfo setObject: @ "Il parametro massimo non è maggiore del parametro minimo" forKey: NSLocalizedDescriptionKey]; // Compila il riferimento all'errore. * error = [[NSError alloc] initWithDomain: codice dominio: errorCode userInfo: userInfo];  return nil;  // Restituisce un numero intero casuale. return [NSNumber numberWithInt: arc4random_uniform ((maximum - minimum) + 1) + minimum];  int main (int argc, const char * argv []) @autoreleasepool errore NSError *; NSNumber * result = generateRandomInteger (0, -10, & error); if (result == nil) // Controlla per vedere cosa è andato storto. NSLog (@ "Si è verificato un errore!"); NSLog (@ "Dominio:% @ Codice:% li", [dominio errore], [codice errore]); NSLog (@ "Descrizione:% @", [error localizedDescription]);  else // Sicuro di usare il valore restituito. NSLog (@ "Numero casuale:% i", [risultato intValue]);  restituisce 0; 

Tutti di localizedDescription, localizedFailureReason, e proprietà correlate di NSError sono effettivamente memorizzati nel suo userInfo dizionario usando tasti speciali definiti da NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, ecc. Quindi, tutto ciò che dobbiamo fare per descrivere l'errore è aggiungere alcune stringhe alle chiavi appropriate, come mostrato nell'ultimo campione.

In genere, è necessario definire le costanti per domini e codici di errore personalizzati in modo che siano coerenti tra le classi.


Sommario

Questo capitolo ha fornito una discussione dettagliata delle differenze tra eccezioni ed errori. Le eccezioni sono progettate per informare i programmatori di problemi fatali nel loro programma, mentre gli errori rappresentano un'azione utente fallita. In generale, dovrebbe essere necessaria un'applicazione pronta per la produzione non lanciare eccezioni, tranne nel caso di circostanze veramente eccezionali (ad es. esaurimento della memoria in un dispositivo).

Abbiamo coperto l'utilizzo di base di NSError, ma tieni presente che esistono diverse classi integrate dedicate all'elaborazione e alla visualizzazione degli errori. Sfortunatamente, questi sono tutti componenti grafici, e quindi al di fuori dello scopo di questo libro. Il iOS in modo succinto sequel ha una sezione dedicata sulla visualizzazione e il recupero dagli errori.

Nell'ultimo capitolo di Obiettivo-C in modo succinto, discuteremo uno degli argomenti più confusi in Objective-C. Scopriremo come i blocchi trattiamo funzionalità nello stesso modo in cui trattiamo dati. Ciò avrà un impatto di vasta portata su ciò che è possibile in un'applicazione Objective-C.

Questa lezione rappresenta un capitolo di Objective-C, un eBook gratuito del team di Syncfusion.