Pracuję nad wyłapywaniem błędów w mojej aplikacji i zamierzam używać NSError
. Jestem nieco zdezorientowany, jak go używać i jak go wypełnić.
Czy ktoś mógłby podać przykład, w jaki sposób wypełnić, a następnie użyć NSError
?
Pracuję nad wyłapywaniem błędów w mojej aplikacji i zamierzam używać NSError
. Jestem nieco zdezorientowany, jak go używać i jak go wypełnić.
Czy ktoś mógłby podać przykład, w jaki sposób wypełnić, a następnie użyć NSError
?
Odpowiedzi:
Cóż, zwykle robię to, aby moje metody, które mogą powodować błędy w czasie wykonywania, odwołują się do NSError
wskaźnika. Jeśli coś rzeczywiście pójdzie nie tak w tej metodzie, mogę wypełnić NSError
odwołanie danymi błędu i zwrócić zero z metody.
Przykład:
- (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;
}
Następnie możemy użyć takiej metody. Nie zawracaj sobie nawet głowy sprawdzaniem obiektu błędu, chyba że metoda zwróci 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.
Udało nam się uzyskać dostęp do błędów, localizedDescription
ponieważ ustawiliśmy wartość dla NSLocalizedDescriptionKey
.
Najlepszym miejscem na więcej informacji jest dokumentacja Apple . To jest naprawdę dobre.
Jest też ładny, prosty samouczek na temat Cocoa Is My Girlfriend .
id
Do BOOL
. Wszelkie drobne zmiany kompatybilne z ARC byłyby bardzo mile widziane.
BOOL
. Zwróć NO
w przypadku błędu i zamiast sprawdzać zwracaną wartość, po prostu sprawdź error
. Jeśli nil
śmiało, to != nil
załatw to.
**error
nie ma wartości zero. W przeciwnym razie program zgłosi błąd, który jest całkowicie nieprzyjazny i nie pokazuje, co się dzieje.
Chciałbym dodać kilka sugestii w oparciu o moją najnowszą implementację. Przejrzałem kod firmy Apple i myślę, że mój kod zachowuje się w podobny sposób.
Powyższe posty już wyjaśniają, jak tworzyć obiekty NSError i zwracać je, więc nie będę zawracał sobie głowy tą częścią. Spróbuję zaproponować dobry sposób na integrację błędów (kodów, komunikatów) we własnej aplikacji.
Zalecam utworzenie 1 nagłówka, który będzie przeglądem wszystkich błędów Twojej domeny (tj. Aplikacji, biblioteki itp.). Mój obecny nagłówek wygląda następująco:
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";
Teraz, używając powyższych wartości błędów, Apple utworzy podstawowy komunikat o błędzie dla Twojej aplikacji. Błąd może zostać utworzony w następujący sposób:
+ (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;
}
Standardowy komunikat błędu wygenerowany przez Apple ( error.localizedDescription
) dla powyższego kodu będzie wyglądał następująco:
Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"
Powyższe jest już bardzo pomocne dla programisty, ponieważ komunikat wyświetla domenę, w której wystąpił błąd i odpowiedni kod błędu. Użytkownicy końcowi nie będą mieli pojęcia, co 1002
oznacza kod błędu , dlatego teraz musimy zaimplementować kilka miłych komunikatów dla każdego kodu.
W przypadku komunikatów o błędach należy pamiętać o lokalizacji (nawet jeśli od razu nie wdrażamy zlokalizowanych komunikatów). W moim obecnym projekcie zastosowałem następujące podejście:
1) utwórz strings
plik, który będzie zawierał błędy. Pliki ciągów można łatwo zlokalizować. Plik może wyglądać następująco:
FSError.strings
"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."
2) Dodaj makra w celu konwersji kodów liczb całkowitych na zlokalizowane komunikaty o błędach. Użyłem 2 makr w moim pliku Constants + Macros.h. MyApp-Prefix.pch
Dla wygody zawsze dołączam ten plik do nagłówka prefiksu ( ).
Stałe + Makra. 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) Teraz łatwo jest wyświetlić przyjazny dla użytkownika komunikat o błędzie oparty na kodzie błędu. Przykład:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
Constants+Macros.h
i importuję ten plik do nagłówka ( .pch
pliku) prefiksu, aby był dostępny wszędzie. Jeśli masz na myśli, że używasz tylko 1 z 2 makr, może to działać. Być może konwersja z int
na NSString
nie jest naprawdę konieczna, chociaż tego nie testowałem.
.strings
pliku), który można zlokalizować , ponieważ tam właśnie będzie wyglądać makro Apple. Przeczytaj o korzystaniu NSLocalizedStringFromTable
tutaj: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
FS_ERROR_LOCALIZED_DESCRIPTION
sprawdza ciąg lokalizowalny w pliku o nazwie FSError.strings
. Możesz sprawdzić przewodnik lokalizacyjny Apple dla .strings
plików, jeśli jest to dla Ciebie obce.
Świetna odpowiedź Alex. Jednym potencjalnym problemem jest dereferencja NULL. Referencje Apple na temat tworzenia i zwracania obiektów 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;
...
Zapoznaj się z następującym samouczkiem
mam nadzieję, że będzie to dla ciebie pomocne, ale zanim będziesz musiał przeczytać dokumentację NSError
To bardzo interesujący link, który niedawno znalazłem ErrorHandling
Spróbuję streścić świetną odpowiedź Alexa i punkt jlmendezbonini, dodając modyfikację, która sprawi, że wszystko będzie kompatybilne z ARC (do tej pory nie jest tak, ponieważ ARC będzie narzekać, ponieważ powinieneś powrócić id
, co oznacza „dowolny obiekt”, ale BOOL
nie jest przedmiotem rodzaj).
- (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;
}
Teraz zamiast sprawdzania wartości zwracanej przez nasze wywołanie metody, sprawdzamy, czy error
nadal jest nil
. Jeśli nie, mamy problem.
// 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.
Inny wzorzec projektowy, który widziałem, obejmuje użycie bloków, co jest szczególnie przydatne, gdy metoda jest uruchamiana asynchronicznie.
Powiedzmy, że mamy zdefiniowane następujące kody błędów:
typedef NS_ENUM(NSInteger, MyErrorCodes) {
MyErrorCodesEmptyString = 500,
MyErrorCodesInvalidURL,
MyErrorCodesUnableToReachHost,
};
Zdefiniowałbyś metodę, która może wywoływać taki błąd:
- (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);
}
}
A kiedy go wywołujesz, nie musisz się martwić o zadeklarowanie obiektu NSError (uzupełnienie kodu zrobi to za Ciebie) lub sprawdzenie zwracanej wartości. Możesz po prostu podać dwa bloki: jeden, który zostanie wywołany, gdy wystąpi wyjątek, i drugi, który zostanie wywołany, gdy się powiedzie:
[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."])
}
}
którego mogę użyć, NSError.defaultError()
gdy nie mam prawidłowego obiektu błędu.
let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.