Qual è il modo migliore per gettare un'eccezione in obiettivo-c / cacao?
Qual è il modo migliore per gettare un'eccezione in obiettivo-c / cacao?
Risposte:
Io uso [NSException raise:format:]
come segue:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Una parola di cautela qui. In Objective-C, a differenza di molti linguaggi simili, in genere si dovrebbe cercare di evitare l'uso di eccezioni per situazioni di errore comuni che possono verificarsi durante il normale funzionamento.
La documentazione di Apple per Obj-C 2.0 afferma quanto segue: "Importante: le eccezioni fanno un uso intensivo delle risorse in Objective-C. Non dovresti usare eccezioni per il controllo generale del flusso o semplicemente per indicare errori (come un file non accessibile)"
La documentazione concettuale sulla gestione delle eccezioni di Apple spiega lo stesso, ma con più parole: "Importante: dovresti riservare l'uso di eccezioni per errori di runtime di programmazione o imprevisti come l'accesso alla raccolta fuori limite, tentativi di mutare oggetti immutabili, invio di un messaggio non valido e la perdita della connessione al server di finestre. In genere si prendono cura di questo tipo di errori con eccezioni quando viene creata un'applicazione anziché in fase di esecuzione. [.....] Invece di eccezioni, oggetti errore (NSError) e Il meccanismo di consegna degli errori di cacao è il modo raccomandato per comunicare gli errori previsti nelle applicazioni di cacao. "
Le ragioni di ciò sono in parte aderire ai linguaggi di programmazione in Objective-C (utilizzando valori di ritorno in casi semplici e parametri di riferimento (spesso la classe NSError) in casi più complessi), in parte il fatto che lanciare e catturare le eccezioni è molto più costoso e infine (e per lo più importante) che le eccezioni Objective-C sono un sottile wrapper attorno alle funzioni setjmp () e longjmp () di C, che in sostanza incasinano la tua attenta gestione della memoria, vedi questa spiegazione .
@throw([NSException exceptionWith…])
Xcode riconosce le @throw
istruzioni come punti di uscita delle funzioni, come le return
istruzioni. L'uso della @throw
sintassi evita errori errati "Il controllo può raggiungere la fine della funzione non nulla " che potresti ricevere [NSException raise:…]
.
Inoltre, @throw
può essere utilizzato per lanciare oggetti che non appartengono alla classe NSException.
Per quanto riguarda [NSException raise:format:]
. Per coloro che provengono da uno sfondo Java, ricorderete che Java distingue tra Exception e RuntimeException. L'eccezione è un'eccezione verificata e RuntimeException non è selezionata. In particolare, Java suggerisce di utilizzare le eccezioni verificate per "condizioni di errore normali" e le eccezioni non selezionate per "errori di runtime causati da un errore del programmatore". Sembra che le eccezioni Objective-C debbano essere utilizzate negli stessi punti in cui si utilizzerebbe un'eccezione non selezionata, mentre i valori di ritorno del codice di errore o i valori NSError sono preferiti nei luoghi in cui si utilizzerà un'eccezione controllata.
Penso che sia coerente, è meglio usare @throw con la tua classe che estende NSException. Quindi usi le stesse notazioni per provare a catturare finalmente:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple spiega qui come generare e gestire le eccezioni: Cattura delle eccezioni Lancio delle eccezioni
A partire da ObjC 2.0, le eccezioni Objective-C non sono più un wrapper per setjmp () longjmp () di C e sono compatibili con l'eccezione C ++, @try è "gratuito", ma lanciare e catturare le eccezioni è molto più costoso.
Ad ogni modo, le asserzioni (usando la famiglia di macro NSAssert e NSCAssert) generano NSException, e questo è ragionevole usarle come stati di Ries.
Utilizzare NSError per comunicare errori anziché eccezioni.
Punti rapidi su NSError:
NSError consente ai codici di errore in stile C (numeri interi) di identificare chiaramente la causa principale e, si spera, consentire al gestore degli errori di superare l'errore. Puoi avvolgere i codici di errore da librerie C come SQLite nelle istanze di NSError molto facilmente.
NSError ha anche il vantaggio di essere un oggetto e offre un modo per descrivere l'errore in modo più dettagliato con il suo membro del dizionario userInfo.
Ma soprattutto, NSError NON PUO 'essere lanciato in modo da incoraggiare un approccio più proattivo alla gestione degli errori, al contrario di altre lingue che semplicemente spingono ulteriormente la patata bollente e aumentano ulteriormente lo stack di chiamate a quel punto può essere segnalato solo all'utente e non gestito in alcun modo significativo (non se credi nel seguire il più grande principio di OOP che nasconde le informazioni).
Link di riferimento : riferimento
Credo che non dovresti mai usare Eccezioni per controllare il normale flusso del programma. Ma dovrebbero essere generate eccezioni ogni volta che un valore non corrisponde a un valore desiderato.
Ad esempio, se una funzione accetta un valore e quel valore non è mai ammesso che sia zero, allora va bene lanciare un'eccezione piuttosto che provare a fare qualcosa di "intelligente" ...
Ries
Dovresti generare eccezioni solo se ti trovi in una situazione che indica un errore di programmazione e vuoi interrompere l'esecuzione dell'applicazione. Pertanto, il modo migliore per generare eccezioni è utilizzare le macro NSAssert e NSParameterAssert e assicurarsi che NS_BLOCK_ASSERTIONS non sia definito.
Codice di esempio per case: @throw ([NSException exceptionWithName: ...
- (void)parseError:(NSError *)error
completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
@try {
NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];
if(!errorData.bytes) {
@throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
resultString = dictFromData[@"someKey"];
...
} @catch (NSException *exception) {
NSLog( @"Caught Exception Name: %@", exception.name);
NSLog( @"Caught Exception Reason: %@", exception.reason );
resultString = exception.reason;
} @finally {
completionBlock(resultString);
}
}
usando:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
Un altro caso d'uso più avanzato:
- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {
NSString *resultString = [NSString new];
NSException* customNilException = [NSException exceptionWithName:@"NilException"
reason:@"object is nil"
userInfo:nil];
NSException* customNotNumberException = [NSException exceptionWithName:@"NotNumberException"
reason:@"object is not a NSNumber"
userInfo:nil];
@try {
NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];
if(!errorData.bytes) {
@throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
}
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
options:NSJSONReadingAllowFragments
error:&error];
NSArray * array = dictFromData[@"someArrayKey"];
for (NSInteger i=0; i < array.count; i++) {
id resultString = array[i];
if (![resultString isKindOfClass:NSNumber.class]) {
[customNotNumberException raise]; // <====== HERE is just the same as: @throw customNotNumberException;
break;
} else if (!resultString){
@throw customNilException; // <======
break;
}
}
} @catch (SomeCustomException * sce) {
// most specific type
// handle exception ce
//...
} @catch (CustomException * ce) {
// most specific type
// handle exception ce
//...
} @catch (NSException *exception) {
// less specific type
// do whatever recovery is necessary at his level
//...
// rethrow the exception so it's handled at a higher level
@throw (SomeCustomException * customException);
} @finally {
// perform tasks necessary whether exception occurred or not
}
}
Non vi è motivo di non utilizzare le eccezioni normalmente nell'obiettivo C anche per indicare le eccezioni alle regole di business. Apple può dire di usare NSError a chi importa. Obj C esiste da molto tempo e un tempo TUTTA la documentazione di C ++ diceva la stessa cosa. Il motivo per cui non importa quanto sia costoso lanciare e catturare un'eccezione, è che la durata di un'eccezione è estremamente breve e ... è un'ECCEZIONE al flusso normale. Non ho mai sentito nessuno dire mai in vita mia, quell'eccezione ha impiegato molto tempo a essere lanciata e catturata.
Inoltre, ci sono persone che pensano che l'obiettivo C stesso sia troppo costoso e invece codifichi in C o C ++. Quindi dire sempre usare NSError è mal informato e paranoico.
Ma alla domanda di questo thread non è ancora stata data risposta qual è il modo MIGLIORE per lanciare un'eccezione. I modi per restituire NSError sono ovvi.
Così è: [NSException raise: ... @throw [[NSException alloc] initWithName .... o @throw [[MyCustomException ...?
Uso la regola selezionata / deselezionata qui in modo leggermente diverso rispetto alla precedente.
La vera differenza tra (usando la metafora java qui) selezionata / deselezionata è importante -> se è possibile recuperare dall'eccezione. E per recupero intendo non solo NON crash.
Quindi uso le classi di eccezioni personalizzate con @throw per eccezioni recuperabili, perché è probabile che avrò un metodo app per cercare alcuni tipi di errori in più blocchi @catch. Ad esempio, se la mia app è un bancomat, avrei un blocco @catch per "WithdrawalRequestExceedsBalanceException".
Uso NSException: rilancio per le eccezioni di runtime poiché non ho modo di recuperare dall'eccezione, se non per prenderlo a un livello superiore e registrarlo. E non ha senso creare una classe personalizzata per questo.
Comunque è quello che faccio, ma se c'è un modo migliore, allo stesso modo espressivo, vorrei sapere anche io. Nel mio codice, da quando ho smesso di scrivere codice C molto tempo fa, non restituisco mai un errore NSE anche se ne ho passato uno da un'API.
@throw([NSException exceptionWith…])
approccio all'approccio in quanto più conciso.