Jak Mogę używać NSError w aplikacji na iPhone ' a?

Pracuję nad wyłapywaniem błędów w mojej aplikacji i szukam użycia NSError. Jestem nieco zdezorientowany, jak go używać i jak go zaludnić.

Czy ktoś mógłby podać przykład jak wypełniam i używam NSError?

Author: Bartłomiej Semańczyk, 2011-01-11

8 answers

Cóż, to, co zwykle robię, to moje metody, które mogą usuwać błędy w czasie wykonywania, biorąc odniesienie do wskaźnika NSError. Jeśli coś rzeczywiście pójdzie nie tak w tej metodzie, mogę wypełnić odniesienie NSError danymi o błędzie i zwrócić nil 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;
}

Możemy wtedy użyć tej metody. Nie trzeba nawet sprawdzać obiektu błędu, chyba że metoda zwróci nil:

// 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łędu localizedDescription, ponieważ ustawiliśmy wartość dla NSLocalizedDescriptionKey.

Najlepszym miejscem na więcej informacji jest dokumentacja Apple. To naprawdę dobre.

Jest też fajny, prosty tutorial o Cocoa Is My Girlfriend .

 456
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-28 05:36:39

Chciałbym dodać kilka sugestii na podstawie mojej najnowszej realizacji. Spojrzałem na jakiś kod od 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ę się przejmował tą częścią. Postaram się zaproponować dobry sposób na integrację błędów (kodów, komunikatów) we własnej aplikacji.


Polecam stworzenie 1 nagłówka, który będzie przeglądem wszystkich błędów Twojego domena (np. aplikacja, biblioteka, itp..). Mój obecny nagłówek wygląda tak:

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, gdy używasz powyższych wartości dla błędów, Apple utworzy podstawowy standardowy komunikat o błędzie dla Twojej aplikacji. Błąd można utworzyć 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 o błędzie generowany 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 odpowiadający mu kod błędu. Użytkownicy końcowi nie będą mieli pojęcia, co oznacza kod błędu 1002, więc teraz musimy zaimplementować kilka ładnych komunikatów dla każdego kodu.

W przypadku komunikatów o błędach musimy pamiętać o lokalizacji(nawet jeśli nie implementujemy komunikatów zlokalizowanych od razu). W moim obecnym projekcie zastosowałem następujące podejście:


1) Utwórz plik strings, który będzie zawierać błędy. Pliki ciągów można łatwo zlokalizować. Plik może wyglądać następująco:

FSError.stringi

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) Dodaj makra do konwersji kodów całkowitych do zlokalizowanych komunikatów o błędach. Użyłem 2 makra w moich stałych + makra.plik H. Zawsze dołączam ten plik do nagłówka prefiksu (MyApp-Prefix.pch) dla wygody.

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 pokazać przyjazny dla użytkownika komunikat o błędzie oparty na kod 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];
 54
Author: Wolfgang Schreurs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-19 14:38:58

Świetna odpowiedź Alex. Jednym z potencjalnych problemów jest dereferencja zerowa. Odsyłacz Apple do 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;
...
 37
Author: jlmendezbonini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-10 20:11:58

Objective-C

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

Swift 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])
 22
Author: AlBeebe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-07 18:06:45

Proszę zapoznać się z poniższym tutorialem

Mam nadzieję, że będzie to pomocne dla ciebie, ale wcześniej musisz przeczytać dokumentację NSError

Bardzo ciekawy link znalazłem ostatnio ErrorHandling

 9
Author: Tirth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-20 06:46:17

Spróbuję podsumować świetną odpowiedź Alexa i punkt jlmendezboniniego, dodając modyfikację, która sprawi, że wszystko będzie kompatybilne z ARC (na razie nie od ARC będzie narzekał, ponieważ powinieneś zwrócić id, co oznacza "dowolny obiekt", ale BOOL nie jest typem obiektu).

- (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 sprawdzać wartość zwracaną wywołania metody, sprawdzamy czy error jest nadal nil. Jeśli nie, to 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.
 3
Author: Gabriele Petronella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-31 01:50:50

Inny wzorzec projektowy, który widziałem, polega na użyciu 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ś swoją metodę, która może wywołać błąd w ten sposób:

- (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 po wywołaniu nie musisz się martwić o zadeklarowanie obiektu NSError (wypełnienie kodu zrobi to za Ciebie) lub sprawdzenie zwracanej wartości. Możesz tylko dostarczyć dwa bloki: jeden, który będzie get called when there is an exception, and one that get called when it successful:

[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");
    }
}];
 3
Author: Senseful,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-08 03:44:50

Cóż jest to trochę poza zakresem pytań, ale w przypadku, gdy nie masz opcji dla NSError zawsze możesz wyświetlić błąd niskiego poziomu:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
 0
Author: Mike.R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-14 14:18:58