在objective-c/cocoa中抛出异常的最佳方法是什么?
我使用 [NSException raise:format:]
如下:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
这里要注意一点。在 Objective-C 中,与许多类似的语言不同,您通常应该尽量避免对正常操作中可能发生的常见错误情况使用异常。
Apple's documentation for Obj-C 2.0 声明如下:“重要提示:异常在 Objective-C 中是资源密集型的。您不应将异常用于一般流程控制,或仅表示错误(例如文件不可访问)”
Apple's conceptual Exception handling documentation 解释相同,但措辞更多:“重要提示:您应该保留将异常用于编程或意外运行时错误,例如越界集合访问、尝试改变不可变对象、发送无效消息以及失去与窗口服务器的连接。您通常在创建应用程序时而不是在运行时处理这些带有异常的错误。[.....] 不是异常,而是错误对象(NSError)和 Cocoa 错误-delivery 机制是在 Cocoa 应用程序中传达预期错误的推荐方式。”
部分原因是为了遵守 Objective-C 中的编程习惯(在简单的情况下使用返回值,在更复杂的情况下使用引用参数(通常是 NSError 类)),部分原因是抛出和捕获异常的成本要高得多,而且最后(并且最重要的是)Objective-C 异常是 C 的 setjmp() 和 longjmp() 函数的一个薄包装,本质上会扰乱您仔细的内存处理,请参阅 this explanation。
@throw([NSException exceptionWith…])
Xcode 将 @throw
语句识别为函数退出点,就像 return
语句一样。使用 @throw
语法可避免您可能从 [NSException raise:…]
收到的错误“Control may reach end of non-void function”警告。
此外,@throw
可用于抛出不属于 NSException 类的对象。
关于[NSException raise:format:]
。对于那些具有 Java 背景的人,您会记得 Java 区分 Exception 和 RuntimeException。 Exception 是已检查的异常,而 RuntimeException 是未检查的。特别是,Java 建议对“正常错误情况”使用检查异常,对“由程序员错误导致的运行时错误”使用未检查异常。似乎 Objective-C 异常应该用在你会使用未经检查的异常的地方,并且错误代码返回值或 NSError 值在你将使用检查的异常的地方是首选的。
我认为要保持一致,最好将@throw 与您自己的扩展 NSException 的类一起使用。然后你在 try catch finally 中使用相同的符号:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple 在这里解释了如何抛出和处理异常:Catching Exceptions Throwing Exceptions
从 ObjC 2.0 开始,Objective-C 异常不再是 C 的 setjmp() longjmp() 的包装器,并且与 C++ 异常兼容,@try 是“免费的”,但抛出和捕获异常的成本要高得多。
无论如何,断言(使用 NSAssert 和 NSCAssert 宏系列)抛出 NSException,并且将它们用作 Ries 状态是明智的。
使用 NSError 来传达失败而不是异常。
关于 NSError 的要点:
NSError 允许 C 风格的错误代码(整数)清楚地识别根本原因,并希望允许错误处理程序克服错误。您可以非常轻松地将来自 SQLite 等 C 库的错误代码包装到 NSError 实例中。
NSError 还具有作为对象的好处,并提供了一种使用其 userInfo 字典成员更详细地描述错误的方法。
但最重要的是,NSError 不能被抛出,因此它鼓励一种更主动的错误处理方法,这与其他语言相反,后者只是将烫手山芋扔得更远,在调用堆栈上只能报告给用户和没有以任何有意义的方式处理(如果您相信遵循 OOP 的最大信息隐藏原则,则不是)。
参考链接:Reference
这是我从“The Big Nerd Ranch Guide (4th edition)”中学到的:
@throw [NSException exceptionWithName:@"Something is not right exception"
reason:@"Can't perform this operation because of this or that"
userInfo:nil];
userInfo:nil
的信息。 :)
您可以使用两种方法在 try catch 块中引发异常
@throw[NSException exceptionWithName];
或第二种方法
NSException e;
[e raise];
我相信你永远不应该使用异常来控制正常的程序流程。但是,只要某个值与所需值不匹配,就应该抛出异常。
例如,如果某个函数接受一个值,并且该值永远不允许为零,那么抛出异常而不是尝试做一些“聪明”的事情是可以的......
里斯
仅当您发现自己处于指示编程错误并希望停止应用程序运行的情况下才应抛出异常。因此,抛出异常的最佳方法是使用 NSAssert 和 NSParameterAssert 宏,并确保未定义 NS_BLOCK_ASSERTIONS。
案例示例代码:@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);
}
}
使用:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
另一个更高级的用例:
- (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
}
}
没有理由不在目标 C 中正常使用异常,甚至表示业务规则异常。苹果可以说使用 NSError 谁在乎。 Obj C 已经存在了很长时间,并且曾经所有的 C++ 文档都说了同样的话。抛出和捕获异常的成本有多高并不重要的原因是异常的生命周期非常短,而且......它是正常流程的一个例外。我一生中从未听过任何人说过,这个异常需要很长时间才能被抛出和捕获。
此外,有些人认为目标 C 本身太昂贵,而是用 C 或 C++ 编写代码。所以说总是使用 NSError 是不明智和偏执的。
但是这个线程的问题还没有回答什么是抛出异常的最佳方法。返回 NSError 的方法是显而易见的。
是这样吗: [NSException raise:... @throw [[NSException alloc] initWithName.... 还是 @throw [[MyCustomException... ?
我在这里使用的检查/未检查规则与上面略有不同。
(在此处使用 java 隐喻)检查/未检查之间的真正区别很重要——>您是否可以从异常中恢复。恢复我的意思不仅仅是不崩溃。
所以我使用带有@throw 的自定义异常类来处理可恢复的异常,因为我很可能会有一些应用程序方法在多个@catch 块中查找某些类型的故障。例如,如果我的应用程序是一台 ATM 机,我将为“WithdrawalRequestExceedsBalanceException”设置一个 @catch 块。
我将 NSException:raise 用于运行时异常,因为我无法从异常中恢复,除了在更高级别捕获它并记录它。并且为此创建一个自定义类是没有意义的。
无论如何,这就是我所做的,但如果有更好的、类似的表达方式,我也想知道。在我自己的代码中,自从我很久以前停止编写 C 代码以来,即使我通过 API 传递了一个 NSError,我也从不返回一个 NSError。
@throw([NSException exceptionWith…])
方法相反,我更喜欢这种方法,因为它更简洁。