Was ist der beste Weg, um eine Ausnahme in Objective-C / Cocoa zu werfen?
Was ist der beste Weg, um eine Ausnahme in Objective-C / Cocoa zu werfen?
Antworten:
Ich benutze [NSException raise:format:]
wie folgt:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Ein Wort der Vorsicht hier. In Objective-C sollten Sie im Gegensatz zu vielen ähnlichen Sprachen generell versuchen, Ausnahmen für häufige Fehlersituationen zu vermeiden, die im normalen Betrieb auftreten können.
In der Apple-Dokumentation zu Obj-C 2.0 heißt es: "Wichtig: Ausnahmen sind in Objective-C ressourcenintensiv. Sie sollten keine Ausnahmen für die allgemeine Flusskontrolle oder einfach zur Kennzeichnung von Fehlern verwenden (z. B. wenn eine Datei nicht zugänglich ist)."
In der konzeptionellen Dokumentation zur Ausnahmebehandlung von Apple wird dasselbe erklärt, jedoch mit mehr Worten: "Wichtig: Sie sollten die Verwendung von Ausnahmen für die Programmierung oder unerwartete Laufzeitfehler wie den Zugriff auf Sammlungen außerhalb der Grenzen, Versuche, unveränderliche Objekte zu mutieren und eine ungültige Nachricht zu senden, reservieren und die Verbindung zum Fensterserver zu verlieren. Normalerweise kümmern Sie sich um diese Art von Fehlern mit Ausnahmen, wenn eine Anwendung erstellt wird, und nicht zur Laufzeit. [.....] Anstelle von Ausnahmen werden Fehlerobjekte (NSError) und die Der Kakaofehler-Übermittlungsmechanismus ist die empfohlene Methode, um erwartete Fehler in Kakaoanwendungen zu kommunizieren. "
Der Grund dafür ist zum einen die Einhaltung von Programmiersprachen in Objective-C (Verwendung von Rückgabewerten in einfachen Fällen und Referenzparametern (häufig die NSError-Klasse) in komplexeren Fällen), zum anderen, dass das Auslösen und Abfangen von Ausnahmen viel teurer ist und Schließlich (und vor allem), dass Objective-C-Ausnahmen ein dünner Wrapper um die Funktionen setjmp () und longjmp () von C sind, der Ihre sorgfältige Speicherbehandlung im Wesentlichen durcheinander bringt, siehe diese Erklärung .
@throw([NSException exceptionWith…])
Xcode erkennt @throw
Anweisungen als Funktionsaustrittspunkte wie return
Anweisungen. Durch die Verwendung der @throw
Syntax werden fehlerhafte Warnungen vermieden , von denen Sie möglicherweise erhalten, dass die Steuerung das Ende der nicht ungültigen Funktion erreicht[NSException raise:…]
.
Auch @throw
kann verwendet werden , um Objekte zu werfen , die nicht der Klasse NSException sind.
In Bezug auf [NSException raise:format:]
. Wenn Sie einen Java-Hintergrund haben, werden Sie sich daran erinnern, dass Java zwischen Exception und RuntimeException unterscheidet. Ausnahme ist eine aktivierte Ausnahme, und RuntimeException ist deaktiviert. Insbesondere schlägt Java vor, aktivierte Ausnahmen für "normale Fehlerbedingungen" und nicht aktivierte Ausnahmen für "Laufzeitfehler, die durch einen Programmiererfehler verursacht werden" zu verwenden. Es scheint, dass Objective-C-Ausnahmen an denselben Stellen verwendet werden sollten, an denen Sie eine ungeprüfte Ausnahme verwenden würden, und Fehlercode-Rückgabewerte oder NSError-Werte werden an Stellen bevorzugt, an denen Sie eine aktivierte Ausnahme verwenden würden.
Ich denke, um konsequent zu sein, ist es besser, @throw mit Ihrer eigenen Klasse zu verwenden, die NSException erweitert. Dann verwenden Sie die gleichen Notationen, um endlich zu versuchen, zu fangen:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple erklärt hier, wie Ausnahmen ausgelöst und behandelt werden: Ausnahmen abfangen Ausnahmen auslösen
Seit ObjC 2.0 sind Objective-C-Ausnahmen kein Wrapper mehr für setjmp () longjmp () von C und mit C ++ - Ausnahmen kompatibel. Das @try ist "kostenlos", aber das Auslösen und Abfangen von Ausnahmen ist weitaus teurer.
Wie auch immer, Behauptungen (unter Verwendung der Makrofamilie NSAssert und NSCAssert) lösen eine NSException aus, und das ist vernünftig, sie als Ries-Zustände zu verwenden.
Verwenden Sie NSError, um Fehler und keine Ausnahmen zu kommunizieren.
Kurze Punkte zu NSError:
NSError ermöglicht Fehlercodes (Ganzzahlen) im C-Stil, um die Grundursache eindeutig zu identifizieren und dem Fehlerbehandler hoffentlich zu ermöglichen, den Fehler zu überwinden. Sie können Fehlercodes aus C-Bibliotheken wie SQLite sehr einfach in NSError-Instanzen einbinden.
NSError hat auch den Vorteil, ein Objekt zu sein, und bietet eine Möglichkeit, den Fehler mit seinem userInfo-Wörterbuchmitglied detaillierter zu beschreiben.
Das Beste ist jedoch, dass NSError NICHT ausgelöst werden kann, sodass ein proaktiverer Ansatz für die Fehlerbehandlung gefördert wird, im Gegensatz zu anderen Sprachen, bei denen die heiße Kartoffel einfach immer weiter nach oben geworfen wird. An diesem Punkt kann sie nur dem Benutzer und gemeldet werden nicht auf sinnvolle Weise behandelt (nicht, wenn Sie daran glauben, OOPs größtem Grundsatz an Informationen zu folgen, die sich verstecken).
Referenzlink: Referenz
Ich glaube, Sie sollten niemals Ausnahmen verwenden, um den normalen Programmfluss zu steuern. Ausnahmen sollten jedoch immer dann ausgelöst werden, wenn ein Wert nicht mit einem gewünschten Wert übereinstimmt.
Wenn zum Beispiel eine Funktion einen Wert akzeptiert und dieser Wert niemals Null sein darf, ist es in Ordnung, eine Ausnahme auszulösen, anstatt zu versuchen, etwas 'Kluges' zu tun ...
Ries
Sie sollten nur Ausnahmen auslösen, wenn Sie sich in einer Situation befinden, die auf einen Programmierfehler hinweist, und die Ausführung der Anwendung stoppen möchten. Daher können Sie Ausnahmen am besten mit den Makros NSAssert und NSParameterAssert auslösen und sicherstellen, dass NS_BLOCK_ASSERTIONS nicht definiert ist.
Beispielcode für 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);
}
}}
Verwenden von:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
Ein weiterer fortgeschrittener Anwendungsfall:
- (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
}
}}
Es gibt keinen Grund, Ausnahmen in Ziel C normalerweise nicht zu verwenden, auch nicht um Geschäftsregelausnahmen zu kennzeichnen. Apple kann sagen, verwenden Sie NSError, wen interessiert das? Obj C gibt es schon lange und zu einer Zeit sagten ALLE C ++ - Dokumentationen dasselbe. Der Grund, warum es keine Rolle spielt, wie teuer das Werfen und Fangen einer Ausnahme ist, ist, dass die Lebensdauer einer Ausnahme außerordentlich kurz ist und ... es eine AUSNAHME vom normalen Ablauf ist. Ich habe noch nie in meinem Leben jemanden sagen hören, Mann, diese Ausnahme hat lange gedauert, bis sie geworfen und gefangen wurde.
Es gibt auch Leute, die denken, dass Ziel C selbst zu teuer ist und stattdessen Code in C oder C ++. Wenn Sie also sagen, dass Sie immer NSError verwenden, ist dies schlecht informiert und paranoid.
Aber die Frage dieses Threads wurde noch nicht beantwortet, was der BESTE Weg ist, eine Ausnahme auszulösen. Die Möglichkeiten, NSError zurückzugeben, liegen auf der Hand.
So ist es: [NSException erhöhen: ... @throw [[NSException alloc] initWithName .... oder @throw [[MyCustomException ...?
Ich verwende die aktivierte / deaktivierte Regel hier etwas anders als oben.
Der wirkliche Unterschied zwischen dem (unter Verwendung der Java-Metapher hier) aktivierten / deaktivierten Element ist wichtig -> ob Sie sich von der Ausnahme erholen können. Und mit erholen meine ich nicht nur NICHT abstürzen.
Daher verwende ich benutzerdefinierte Ausnahmeklassen mit @throw für wiederherstellbare Ausnahmen, da ich wahrscheinlich eine App-Methode haben werde, die nach bestimmten Arten von Fehlern in mehreren @ catch-Blöcken sucht. Wenn meine App beispielsweise ein Geldautomat ist, hätte ich einen @ catch-Block für die "WithdrawalRequestExceedsBalanceException".
Ich verwende NSException: Raise für Laufzeitausnahmen, da ich keine Möglichkeit habe, die Ausnahme wiederherzustellen, außer sie auf einer höheren Ebene abzufangen und zu protokollieren. Und es macht keinen Sinn, dafür eine benutzerdefinierte Klasse zu erstellen.
Jedenfalls ist es das, was ich tue, aber wenn es einen besseren, ähnlich ausdrucksstarken Weg gibt, würde ich es auch gerne wissen. In meinem eigenen Code gebe ich, da ich vor langer Zeit aufgehört habe, C a hella zu codieren, niemals einen NSError zurück, selbst wenn mir einer von einer API übergeben wird.
@throw([NSException exceptionWith…])
Ansatz.