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 erroriAd 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.
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.
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.
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:
#importareint 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 rilevataSuccessivamente, impareremo come rilevare le eccezioni e impedire la chiusura del programma.
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.
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.
#importareint 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:
#importareint 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.
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.
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.
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.
.
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:
NSError
variabile. Non è necessario allocarlo o inizializzarlo.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.
#importareint 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.
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.
#importareNSNumber * 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.
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.