Wie kann ich NSError in meiner iPhone App verwenden?

228

Ich arbeite daran, Fehler in meiner App zu erkennen, und ich prüfe die Verwendung NSError. Ich bin etwas verwirrt darüber, wie man es benutzt und wie man es bevölkert.

Könnte jemand ein Beispiel dafür geben, wie ich es ausfülle und dann verwende NSError?

Nic Hubbard
quelle

Antworten:

473

Normalerweise lassen meine Methoden, die zur Laufzeit fehlerhaft werden können, auf einen NSErrorZeiger verweisen . Wenn bei dieser Methode tatsächlich etwas schief geht, kann ich die NSErrorReferenz mit Fehlerdaten füllen und null von der Methode zurückgeben.

Beispiel:

- (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;
}

Wir können dann die Methode so verwenden. Machen Sie sich nicht einmal die Mühe, das Fehlerobjekt zu untersuchen, es sei denn, die Methode gibt nil zurück:

// 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.

Wir konnten auf die Fehler zugreifen, localizedDescriptionweil wir einen Wert für festgelegt haben NSLocalizedDescriptionKey.

Der beste Ort für weitere Informationen ist die Dokumentation von Apple . Es ist wirklich gut.

Es gibt auch ein schönes, einfaches Tutorial zu Cocoa Is My Girlfriend .

Alex
quelle
37
Dies ist die am meisten Spaß Beispiel je
ming yeow
Dies ist eine ziemlich großartige Antwort, obwohl es einige Probleme in ARC gibt und das Casting idauf a BOOL. Jede geringfügige ARC-kompatible Variation wäre sehr willkommen.
NSTJ
6
@ TomJowett Ich wäre wirklich sauer, wenn wir den Welthunger nicht beenden könnten, nur weil Apple uns dazu gedrängt hat, in die neuere ARC-Welt zu wechseln.
Manav
1
Der Rückgabetyp kann sein BOOL. Rückgabe NOim Fehlerfall und anstatt nach dem Rückgabewert zu suchen, prüfen Sie einfach nach error. Wenn Sie nilweitermachen, wenn Sie damit != nilumgehen.
Gabriele Petronella
8
-1: Sie müssen wirklich Code einbinden, der überprüft, dass er **errornicht Null ist. Andernfalls gibt das Programm einen Fehler aus, der völlig unfreundlich ist und nicht deutlich macht, was passiert.
FreeAsInBeer
58

Ich möchte einige weitere Vorschläge hinzufügen, die auf meiner letzten Implementierung basieren. Ich habe mir einen Code von Apple angesehen und denke, mein Code verhält sich ähnlich.

In den obigen Beiträgen wird bereits erläutert, wie NSError-Objekte erstellt und zurückgegeben werden, sodass ich mich nicht mit diesem Teil befassen werde. Ich werde nur versuchen, einen guten Weg vorzuschlagen, um Fehler (Codes, Nachrichten) in Ihre eigene App zu integrieren.


Ich empfehle, einen Header zu erstellen, der eine Übersicht über alle Fehler Ihrer Domain (z. B. App, Bibliothek usw.) enthält. Mein aktueller Header sieht folgendermaßen aus:

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";

Wenn Sie nun die oben genannten Werte für Fehler verwenden, erstellt Apple einige grundlegende Standardfehlermeldungen für Ihre App. Ein Fehler kann wie folgt erstellt werden:

+ (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;
}

Die von Apple generierte Standardfehlermeldung ( error.localizedDescription) für den obigen Code sieht folgendermaßen aus:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

Das Obige ist für Entwickler bereits sehr hilfreich, da in der Nachricht die Domäne angezeigt wird, in der der Fehler aufgetreten ist, und der entsprechende Fehlercode. Endbenutzer haben jedoch keine Ahnung, was Fehlercode 1002bedeutet. Daher müssen wir jetzt für jeden Code einige nette Nachrichten implementieren.

Bei den Fehlermeldungen müssen wir die Lokalisierung berücksichtigen (auch wenn wir lokalisierte Nachrichten nicht sofort implementieren). Ich habe in meinem aktuellen Projekt den folgenden Ansatz verwendet:


1) Erstellen Sie eine stringsDatei, die die Fehler enthält. Strings-Dateien sind leicht zu lokalisieren. Die Datei könnte folgendermaßen aussehen:

FSError.strings

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

2) Fügen Sie Makros hinzu, um Ganzzahlcodes in lokalisierte Fehlermeldungen umzuwandeln. Ich habe 2 Makros in meiner Datei Constants + Macros.h verwendet. Ich füge diese Datei der Einfachheit halber immer in den Präfix-Header ( MyApp-Prefix.pch) ein.

Konstanten + Makros.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) Jetzt ist es einfach, eine benutzerfreundliche Fehlermeldung basierend auf einem Fehlercode anzuzeigen. Ein Beispiel:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];
Wolfgang Schreurs
quelle
9
Gute Antwort! Aber warum nicht die lokalisierte Beschreibung in das Benutzerinformationswörterbuch einfügen, wo sie hingehört? [NSError errorWithDomain: FSMyAppErrorDomain-Code: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (error.code)}];
Richard Venable
1
Gibt es einen bestimmten Ort, an dem ich die String-Datei ablegen soll? Von FS_ERROR_LOCALIZED_DESCRIPTION () erhalte ich nur die Nummer (Fehlercode).
Huggie
@huggie: nicht wirklich sicher, was du meinst. Normalerweise füge ich diese Makros, die ich in der gesamten App verwende, in eine aufgerufene Datei ein Constants+Macros.hund importiere diese Datei in den Präfix-Header (die .pchDatei), damit sie überall verfügbar ist. Wenn Sie meinen, dass Sie nur eines der beiden Makros verwenden, funktioniert dies möglicherweise. Vielleicht ist die Konvertierung von intnach NSStringnicht wirklich notwendig, obwohl ich dies nicht getestet habe.
Wolfgang Schreurs
@huggie: Oh, ich glaube ich verstehe dich jetzt. Die Zeichenfolgen sollten sich in einer lokalisierbaren Datei ( .stringsDatei) befinden, da dort das Makro von Apple aussehen wird. Lesen Sie hier mehr über die Verwendung NSLocalizedStringFromTable: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Wolfgang Schreurs
1
@huggie: Ja, ich habe die lokalisierten String-Tabellen verwendet. Der Code im Makro FS_ERROR_LOCALIZED_DESCRIPTIONüberprüft die lokalisierbare Zeichenfolge in einer aufgerufenen Datei FSError.strings. Vielleicht möchten Sie Apples Lokalisierungshandbuch für .stringsDateien lesen, wenn Ihnen dies fremd ist.
Wolfgang Schreurs
37

Tolle Antwort Alex. Ein mögliches Problem ist die NULL-Dereferenzierung. Apples Referenz zum Erstellen und Zurückgeben von NSError-Objekten

...
[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;
...
jlmendezbonini
quelle
30

Ziel 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"])
AlBeebe
quelle
9

Bitte beziehen Sie sich auf das folgende Tutorial

Ich hoffe, es wird für Sie hilfreich sein, aber bevor Sie die Dokumentation von NSError lesen müssen

Dies ist ein sehr interessanter Link, den ich kürzlich gefunden habe. ErrorHandling

Tirth
quelle
3

Ich werde versuchen, die großartige Antwort von Alex und den Punkt von jlmendezbonini zusammenzufassen und eine Modifikation hinzuzufügen, die alles ARC-kompatibel macht (bisher ist es nicht so, dass ARC sich beschwert, da Sie zurückkehren sollten id, was "jedes Objekt" bedeutet, aber BOOLkein Objekt ist Art).

- (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;
}

Anstatt jetzt nach dem Rückgabewert unseres Methodenaufrufs zu suchen, prüfen wir, ob dies errornoch der Fall ist nil. Wenn nicht, haben wir ein 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.
Gabriele Petronella
quelle
3
@Gabriela: Apple gibt an, dass bei Verwendung von Indirektionsvariablen zur Rückgabe von Fehlern die Methode selbst bei Erfolg oder Misserfolg immer einen Rückgabewert haben sollte. Apple fordert die Entwickler dringend auf, zuerst nach dem Rückgabewert zu suchen und nur dann, wenn der Rückgabewert irgendwie ungültig ist, auf Fehler zu prüfen. Siehe die folgende Seite: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Wolfgang Schreurs
3

Ein anderes Entwurfsmuster, das ich gesehen habe, beinhaltet die Verwendung von Blöcken, was besonders nützlich ist, wenn eine Methode asynchron ausgeführt wird.

Angenommen, wir haben die folgenden Fehlercodes definiert:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

Sie würden Ihre Methode definieren, die einen Fehler wie folgt auslösen kann:

- (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);
    }
}

Und wenn Sie es dann aufrufen, müssen Sie sich nicht darum kümmern, das NSError-Objekt zu deklarieren (die Code-Vervollständigung erledigt dies für Sie) oder den Rückgabewert zu überprüfen. Sie können einfach zwei Blöcke angeben: einen, der aufgerufen wird, wenn eine Ausnahme vorliegt, und einen, der aufgerufen wird, wenn dies erfolgreich ist:

[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");
    }
}];
Sinnvoll
quelle
0

Nun, es kommt ein wenig außer Frage, aber falls Sie keine Option für NSError haben, können Sie immer den Low-Level-Fehler anzeigen:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
Mike.R
quelle
0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

die ich verwenden kann, NSError.defaultError()wenn ich kein gültiges Fehlerobjekt habe.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Hemang
quelle