Estou trabalhando na captura de erros no meu aplicativo e estou pensando em usá-lo NSError
. Estou um pouco confuso sobre como usá-lo e como preenchê-lo.
Alguém poderia fornecer um exemplo de como eu preencho e uso NSError
?
Estou trabalhando na captura de erros no meu aplicativo e estou pensando em usá-lo NSError
. Estou um pouco confuso sobre como usá-lo e como preenchê-lo.
Alguém poderia fornecer um exemplo de como eu preencho e uso NSError
?
Respostas:
Bem, o que eu costumo fazer é ter meus métodos que poderiam gerar erros no tempo de execução fazer referência a um NSError
ponteiro. Se algo realmente der errado nesse método, eu posso preencher a NSError
referência com dados de erro e retornar nulo do método.
Exemplo:
- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
// begin feeding the world's children...
// it's all going well until....
if (ohNoImOutOfMonies) {
// sad, we can't solve world hunger, but we can let people know what went wrong!
// init dictionary to be used to populate error object
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
*error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
}
// wohoo! We fed the world's children. The world is now in lots of debt. But who cares?
return YES;
}
Podemos então usar o método como este. Nem se preocupe em inspecionar o objeto de erro, a menos que o método retorne zero:
// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
// inspect error
NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Conseguimos acessar os erros localizedDescription
porque definimos um valor para NSLocalizedDescriptionKey
.
O melhor lugar para obter mais informações é a documentação da Apple . É realmente bom.
Há também um tutorial simples e agradável sobre Cocoa Is My Girlfriend .
id
para a BOOL
. Qualquer leve variação compatível com ARC seria muito apreciada.
BOOL
. Retorne NO
em caso de erro e, em vez de verificar o valor de retorno, basta verificar error
. Se nil
seguir em frente, se != nil
lidar com isso.
**error
se não é nulo. Caso contrário, o programa lançará um erro que é completamente hostil e não torna aparente o que está acontecendo.
Gostaria de adicionar mais algumas sugestões com base na minha implementação mais recente. Eu olhei para alguns códigos da Apple e acho que meu código se comporta da mesma maneira.
As postagens acima já explicam como criar objetos NSError e devolvê-los, para não me preocupar com essa parte. Vou apenas tentar sugerir uma boa maneira de integrar erros (códigos, mensagens) em seu próprio aplicativo.
Eu recomendo criar um cabeçalho que seja uma visão geral de todos os erros do seu domínio (por exemplo, aplicativo, biblioteca etc.). Meu cabeçalho atual é assim:
FSError.h
FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;
enum {
FSUserNotLoggedInError = 1000,
FSUserLogoutFailedError,
FSProfileParsingFailedError,
FSProfileBadLoginError,
FSFNIDParsingFailedError,
};
FSError.m
#import "FSError.h"
NSString *const FSMyAppErrorDomain = @"com.felis.myapp";
Agora, ao usar os valores acima para erros, a Apple criará uma mensagem de erro padrão básica para seu aplicativo. Um erro pode ser criado como o seguinte:
+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
if (profileInfo)
{
/* ... lots of parsing code here ... */
if (profileInfo.username == nil)
{
*error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];
return nil;
}
}
return profileInfo;
}
A mensagem de erro padrão gerada pela Apple ( error.localizedDescription
) para o código acima será semelhante à seguinte:
Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"
O exposto acima já é bastante útil para um desenvolvedor, pois a mensagem exibe o domínio em que o erro ocorreu e o código de erro correspondente. Os usuários finais não têm idéia do 1002
significado do código de erro , então agora precisamos implementar algumas boas mensagens para cada código.
Para as mensagens de erro, precisamos manter a localização em mente (mesmo que não implementemos mensagens localizadas imediatamente). Eu usei a seguinte abordagem no meu projeto atual:
1) crie um strings
arquivo que conterá os erros. Arquivos de strings são facilmente localizáveis. O arquivo pode ter a seguinte aparência:
FSError.strings
"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."
2) Adicione macros para converter códigos inteiros em mensagens de erro localizadas. Eu usei 2 macros no meu arquivo Constants + Macros.h. Eu sempre incluo esse arquivo no cabeçalho do prefixo ( MyApp-Prefix.pch
) por conveniência.
Constantes + Macros.h
// error handling ...
#define FS_ERROR_KEY(code) [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code) NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)
3) Agora é fácil mostrar uma mensagem de erro amigável com base em um código de erro. Um exemplo:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
Constants+Macros.h
e importo esse arquivo no cabeçalho do prefixo ( .pch
arquivo) para que fique disponível em qualquer lugar. Se você quer dizer que está usando apenas uma das duas macros, isso pode funcionar. Talvez a conversão de int
para NSString
não seja realmente necessária, embora eu não tenha testado isso.
.strings
arquivo), pois é para lá que a macro da Apple ficará. Leia sobre como usar NSLocalizedStringFromTable
aqui: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
FS_ERROR_LOCALIZED_DESCRIPTION
verifica a cadeia localizável em um arquivo chamado FSError.strings
. Você pode conferir o guia de localização da Apple em .strings
arquivos, se isso lhe for estranho.
Ótima resposta Alex. Um problema em potencial é a desreferência nula. Referência da Apple sobre como criar e retornar objetos NSError
...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
if (error != NULL) {
// populate the error object with the details
*error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...
Consulte o seguinte tutorial
Espero que seja útil para você, mas antes você precisa ler a documentação do NSError
Este é um link muito interessante que encontrei recentemente ErrorHandling
Tentarei resumir a ótima resposta de Alex e o argumento do jlmendezbonini, adicionando uma modificação que tornará tudo compatível com o ARC (até agora não é porque o ARC irá reclamar, pois você deve retornar id
, o que significa "qualquer objeto", mas BOOL
não é um objeto tipo).
- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
// begin feeding the world's children...
// it's all going well until....
if (ohNoImOutOfMonies) {
// sad, we can't solve world hunger, but we can let people know what went wrong!
// init dictionary to be used to populate error object
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
if (error != NULL) {
// populate the error object with the details
*error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return NO;
}
// wohoo! We fed the world's children. The world is now in lots of debt. But who cares?
return YES;
}
Agora, em vez de verificar o valor de retorno de nossa chamada de método, verificamos se error
ainda está nil
. Se não for, temos um problema.
// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
// inspect error
NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Outro padrão de design que eu já vi envolve o uso de blocos, o que é especialmente útil quando um método está sendo executado de forma assíncrona.
Digamos que tenhamos os seguintes códigos de erro definidos:
typedef NS_ENUM(NSInteger, MyErrorCodes) {
MyErrorCodesEmptyString = 500,
MyErrorCodesInvalidURL,
MyErrorCodesUnableToReachHost,
};
Você definiria seu método que pode gerar um erro da seguinte maneira:
- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
if (path.length == 0) {
if (failure) {
failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
}
return;
}
NSString *htmlContents = @"";
// Exercise for the reader: get the contents at that URL or raise another error.
if (success) {
success(htmlContents);
}
}
E quando você o chama, não precisa se preocupar em declarar o objeto NSError (a conclusão do código fará isso por você) ou em verificar o valor retornado. Você pode fornecer apenas dois blocos: um que será chamado quando houver uma exceção e outro que será chamado quando for bem-sucedido:
[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
NSLog(@"Failed to get contents: %@", error);
if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
NSLog(@"You must provide a non-empty string");
}
}];
extension NSError {
static func defaultError() -> NSError {
return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
}
}
que posso usar NSError.defaultError()
sempre que não tiver um objeto de erro válido.
let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.