lançando uma exceção no objetivo-c / cacau

Respostas:

528

Eu uso da [NSException raise:format:]seguinte maneira:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
e.James
fonte
9
Prefiro esse caminho ao contrário da @throw([NSException exceptionWith…])abordagem, pois é mais concisa.
Sam Soffes 18/06/10
9
Certifique-se de ler a advertência importante de danos ( stackoverflow.com/questions/324284/324805#324805 )
e.James
26
Eu geralmente prefiro isso também, mas há uma pegadinha. Pode ser apenas minha versão atual do Xcode, mas a sintaxe [NSException raise ...] não parece ser reconhecida pelo analisador como um caminho de saída de um método que retorna um valor. Estou vendo o aviso "O controle pode chegar ao final da função não nula" ao usar esta sintaxe, mas com a sintaxe @throw ([NSException exceptionWith ...]), o analisador reconhece isso como uma saída e não exibe o aviso.
mattorb
1
@mpstx Eu sempre uso a sintaxe throw pela razão que você deu (o que ainda é relevante dois anos depois no Xcode 4.6, e provavelmente sempre será). Fazer com que o IDE reconheça que lançar uma exceção é um ponto de saída da função frequentemente importa se você deseja evitar avisos.
Mark Amery
FWIW Estou percebendo que os blocos @ try / @ catch também resultam em falso negativo para os avisos "o controle atinge o fim da função não nula" (ou seja, o aviso não é exibido quando deveria ser)
Brian Gerstle
256

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 .

prejudica
fonte
11
Eu acho que isso se aplica à maioria das linguagens de programação: "tente evitar o uso de exceções para situações de erro comuns". O mesmo se aplica em Java; é uma prática recomendada lidar com erros de entrada do usuário (por exemplo), com exceções. Não apenas pelo uso de recursos, mas também pela clareza do código.
Beetstra
6
Mais importante, as exceções no cacau são projetadas para indicar erros de programa não recuperáveis. Fazer o contrário é executado na estrutura e pode levar a um comportamento indefinido. Consulte stackoverflow.com/questions/3378696/iphone-try-end-try/… para obter detalhes.
KPM
9
"O mesmo se aplica em Java;" Discordo. Você pode usar exceções verificadas em Java muito bem para condições de erro normais. Claro que você não usaria exceções de tempo de execução.
Daniel Ryan
Eu prefiro a maneira Cocoa (as exceções são apenas para erros do programador), então prefiro fazê-lo em Java também, mas a realidade é que você deve seguir práticas típicas em um ambiente, e as exceções para o tratamento de erros se destacam como um mau cheiro no Objective-C, mas são muito usados ​​para esse fim em Java.
gnasher729
1
Este comentário não responde a pergunta. Talvez o OP só queira travar o aplicativo para testar se a estrutura do relatório de falhas está funcionando conforme o esperado.
19615 Simon
62
@throw([NSException exceptionWith…])

O Xcode reconhece @throwinstruções como pontos de saída da função, como returninstruções. O uso da @throwsintaxe 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, @throwpode ser usado para lançar objetos que não são da classe NSException.

Peter Hosey
fonte
11
@Steph Thirion: consulte developer.apple.com/documentation/Cocoa/Conceptual/Exceptions/… para obter todos os detalhes. Bottom line? Ambos funcionarão, mas @throw pode ser usado para lançar objetos que não são da classe NSException.
e.James
33

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.

Daniel Yankowsky
fonte
1
Sim, isso está correto (após 4 anos: D), crie sua própria classe de erro ABCError que se estende da classe NSError e use-a para exceções verificadas, em vez de NSExceptions. Crie NSExceptions em que ocorrem erros do programador (situação inesperada, como um problema de formato numérico).
Chathuram
15

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

enferrujado
fonte
eu ainda tenho acidente por exceção tempo de execução no bloco try
famfamfam
14

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.

Psicopata
fonte
Bom saber! Temos uma biblioteca de terceiros que não queremos modificar que gera exceções até para os menores erros. Temos que pegá-los em um lugar no aplicativo e isso apenas nos faz estremecer, mas isso me faz sentir um pouco melhor.
Yuri Brigance
8

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

Jason Fuerstenberg
fonte
Este comentário não responde a pergunta. Talvez o OP só queira travar o aplicativo para testar se a estrutura do relatório de falhas está funcionando conforme o esperado.
19615 Simon
7

Foi assim que aprendi no "The Big Nerd Ranch Guide (4th edition)":

@throw [NSException exceptionWithName:@"Something is not right exception"
                               reason:@"Can't perform this operation because of this or that"
                             userInfo:nil];
Johannes
fonte
OK, mas não diz muito sobre o userInfo:nil. :)
Cœur
6

Você pode usar dois métodos para gerar exceção no bloco try catch

@throw[NSException exceptionWithName];

ou o segundo método

NSException e;
[e raise];
Subbu
fonte
3

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

R. van Twisk
fonte
0

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.

gnasher729
fonte
0

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

}

}

Aleksandr B.
fonte
-7

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.

usuário deletado
fonte
4
Eu recomendaria tentar programar um servidor com exceções como parte do fluxo normal de casos de erro antes de fazer declarações generalizadas como "não há motivo para não usar exceções normalmente no objetivo C". Acredite ou não, há razões para escrever aplicativos de alto desempenho (ou pelo menos partes de aplicativos) no ObjC, e lançar exceções normalmente prejudica seriamente o desempenho.
jbenet
6
De fato, existem boas razões para não usar exceções no cacau. Consulte a resposta de Bill Bumgarner aqui para obter mais informações: stackoverflow.com/questions/3378696/iphone-try-end-try/… . Ele sabe do que está falando (dica: verifique o empregador). Exceções no cacau são tratadas como erros irrecuperáveis ​​e podem deixar o sistema em um estado instável. O NSError é o caminho a percorrer para passar erros gerais.
Brad Larson
Exceções são excepcionais . As falhas nas regras de negócios certamente não se qualificam. "Encontrar e projetar códigos pesados ​​de exceção podem resultar em uma vitória decente no desempenho". MSDN via codinghorror.com/blog/2004/10/…
Jonathan Watmough
3
Exceções não podem ser lançadas a partir de blocos. Exceções lançadas em um ambiente ARC podem fazer o seu programa vazar. Assim, o voto negativo.
Moszi
"não importa o quão caro lançar e capturar uma exceção seja", estou escrevendo um emulador em que o desempenho é crítico. Não posso lançar um monte de exceções caras.
NobodyNada