Qual é a melhor maneira de lançar uma exceção no objetivo-c / cacau?
Qual é a melhor maneira de lançar uma exceção no objetivo-c / cacau?
Respostas:
Eu uso da [NSException raise:format:]
seguinte maneira:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Uma palavra de cautela aqui. No Objective-C, diferentemente de muitos idiomas semelhantes, você geralmente deve tentar evitar exceções para situações de erro comuns que podem ocorrer em operação normal.
A documentação da Apple para o Obj-C 2.0 afirma o seguinte: "Importante: Exceções consomem muitos recursos no Objective-C. Você não deve usar exceções para controle de fluxo geral ou simplesmente para significar erros (como um arquivo não acessível)"
A documentação conceitual sobre manipulação de exceções da Apple explica o mesmo, mas com mais palavras: "Importante: você deve reservar o uso de exceções para programação ou erros inesperados de tempo de execução, como acesso fora da área de coleta, tentativas de alterar objetos imutáveis, enviando uma mensagem inválida e perdendo a conexão com o servidor de janelas.Você geralmente cuida desses tipos de erros com exceções quando um aplicativo está sendo criado, e não em tempo de execução. [.....] Em vez de exceções, os objetos de erro (NSError) e o O mecanismo de entrega de erros de cacau é a maneira recomendada de comunicar os erros esperados nos aplicativos de cacau. "
As razões para isso são, em parte, aderir aos idiomas de programação no Objective-C (usando valores de retorno em casos simples e parâmetros por referência (geralmente a classe NSError) em casos mais complexos), em parte que lançar e capturar exceções é muito mais caro e finalmente (e é mais importante ainda) que as exceções de Objective-C são um invólucro fino em torno das funções setjmp () e longjmp () de C, essencialmente atrapalhando seu tratamento cuidadoso da memória, consulte esta explicação .
@throw([NSException exceptionWith…])
O Xcode reconhece @throw
instruções como pontos de saída da função, como return
instruções. O uso da @throw
sintaxe evita avisos errôneos de "O controle pode chegar ao fim da função não nula " que você pode obter [NSException raise:…]
.
Além disso, @throw
pode ser usado para lançar objetos que não são da classe NSException.
Em relação [NSException raise:format:]
. Para quem vem de um plano de fundo Java, você deve se lembrar que Java distingue entre Exception e RuntimeException. Exceção é uma exceção verificada e RuntimeException está desmarcada. Em particular, Java sugere o uso de exceções verificadas para "condições normais de erro" e exceções não verificadas para "erros de tempo de execução causados por um erro do programador". Parece que as exceções de Objective-C devem ser usadas nos mesmos locais em que você usaria uma exceção desmarcada, e os valores de retorno do código de erro ou NSError são preferidos nos locais em que você usaria uma exceção verificada.
Eu acho que para ser consistente, é melhor usar o @throw com sua própria classe que estende o NSException. Então você usa as mesmas notações para finalmente tentar pegar:
@try {
.....
}
@catch{
...
}
@finally{
...
}
A Apple explica aqui como lançar e manipular exceções: Capturando exceções Lançando exceções
Desde o ObjC 2.0, as exceções Objective-C não são mais um invólucro para setjmp () longjmp () e são compatíveis com a exceção C ++, o @try é "gratuito", mas lançar e capturar exceções é muito mais caro.
De qualquer forma, as asserções (usando a família de macro NSAssert e NSCAssert) geram NSException, e é sensato usá-las como estados Ries.
Use o NSError para comunicar falhas em vez de exceções.
Pontos rápidos sobre o NSError:
O NSError permite que códigos de erro no estilo C (números inteiros) identifiquem claramente a causa raiz e esperemos que permita que o manipulador de erros supere o erro. É possível agrupar códigos de erro de bibliotecas C como SQLite em instâncias do NSError com muita facilidade.
O NSError também tem o benefício de ser um objeto e oferece uma maneira de descrever o erro em mais detalhes com seu membro do dicionário userInfo.
Mas o melhor de tudo é que o NSError NÃO PODE ser lançado, por isso encoraja uma abordagem mais proativa ao tratamento de erros, em contraste com outros idiomas que simplesmente jogam a batata quente mais longe e aumentam a pilha de chamadas, momento em que ela só pode ser relatada ao usuário e não tratado de maneira significativa (não se você acredita em seguir o maior princípio de ocultação de informações da OOP).
Link de referência : Referência
Eu acredito que você nunca deve usar exceções para controlar o fluxo normal do programa. Porém, exceções devem ser lançadas sempre que algum valor não corresponder ao valor desejado.
Por exemplo, se alguma função aceita um valor, e esse valor nunca pode ser nulo, então é bom lançar uma exceção em vez de tentar fazer algo 'inteligente' ...
Ries
Você só deve lançar exceções se se encontrar em uma situação que indica um erro de programação e desejar interromper a execução do aplicativo. Portanto, a melhor maneira de lançar exceções é usar as macros NSAssert e NSParameterAssert e garantir que NS_BLOCK_ASSERTIONS não esteja definido.
Código de exemplo para 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);
}];
Outro caso de uso mais avançado:
- (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
}
}
Não há razão para não usar exceções normalmente no objetivo C, mesmo para significar exceções de regras de negócios. A Apple pode dizer que use o NSError quem se importa. O Obj C existe há muito tempo e, ao mesmo tempo, TODA a documentação do C ++ dizia a mesma coisa. A razão pela qual não importa o quão caro lançar e capturar uma exceção é, é o tempo de vida de uma exceção é extremamente curto e ... é uma EXCEÇÃO ao fluxo normal. Eu nunca ouvi alguém dizer alguma vez na minha vida, cara essa exceção demorou muito tempo para ser jogada e pega.
Além disso, existem pessoas que acham que o objetivo C em si é muito caro e, em vez disso, codificam em C ou C ++. Dizer que sempre usar NSError é mal informado e paranóico.
Mas a questão dessa discussão ainda não foi respondida, qual é a MELHOR maneira de lançar uma exceção. As maneiras de retornar o NSError são óbvias.
Assim é: [NSException raise: ... @throw [[NSException aloc] initWithName .... ou @throw [[MyCustomException ...?
Eu uso a regra marcada / desmarcada aqui de maneira um pouco diferente da anterior.
A diferença real entre a (usando a metáfora java aqui) marcada / desmarcada é importante -> se você pode se recuperar da exceção. E por recuperação, quero dizer não apenas não travar.
Então, eu uso classes de exceção personalizadas com @throw para exceções recuperáveis, porque é provável que eu tenha algum método de aplicativo procurando por certos tipos de falhas em vários blocos @catch. Por exemplo, se meu aplicativo for um caixa eletrônico, eu teria um bloco @catch para a "WithdrawalRequestExceedsBalanceException".
Eu uso o NSException: raise para exceções de tempo de execução, pois não tenho como me recuperar da exceção, exceto para capturá-la em um nível superior e registrá-la. E não faz sentido criar uma classe personalizada para isso.
Enfim, é isso que eu faço, mas se houver uma maneira melhor e igualmente expressiva, eu gostaria de saber também. No meu próprio código, desde que parei de codificar C há muito tempo, nunca retornei um NSError, mesmo que eu tenha passado um por uma API.
@throw([NSException exceptionWith…])
abordagem, pois é mais concisa.