在objective-c/cocoa中抛出异常

在objective-c/cocoa中抛出异常的最佳方法是什么?

142346 次浏览

我使用[NSException raise:format:]如下所示:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
@throw([NSException exceptionWith…])

Xcode将@throw语句识别为函数出口点,就像return语句一样。使用@throw语法可以避免从[NSException raise:…]中得到错误的“控制可以到达非空函数的末端”警告。

同样,@throw可以用来抛出不属于NSException类的对象。

我认为为了保持一致性,最好在你自己的类中使用@throw,它扩展了NSException。然后使用相同的表示法try catch finally:

@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple解释了如何抛出和处理异常: catch Exceptions . 抛出异常 < / p >

这里有一个警告。在Objective-C中,与许多类似的语言不同,您通常应该尽量避免在正常操作中可能发生的常见错误情况下使用异常。

苹果Obj-C 2.0的文档声明如下:“重要:异常在Objective-C中是资源密集型的。您不应将异常用于一般的流控制,或仅用于表示错误(例如文件不可访问)”

Apple概念性异常处理文档解释了同样的道理,但用了更多的话:“重要:你应该为编程或意外的运行时错误保留异常的使用,比如越界的集合访问,试图改变不可变对象,发送无效消息,以及失去到窗口服务器的连接。通常在创建应用程序时而不是在运行时处理这些类型的异常错误。[…错误对象(NSError)和Cocoa错误传递机制是在Cocoa应用程序中传达预期错误的推荐方式,而不是异常。”

这样做的部分原因是坚持Objective-C中的编程习惯(在简单的情况下使用返回值,在更复杂的情况下使用按引用参数(通常是NSError类)),部分原因是抛出和捕获异常的代价要高得多,最后(可能是最重要的)Objective-C异常是C的setjmp()和longjmp()函数的瘦包装,本质上搞砸了你仔细的内存处理,参见这个解释

关于[NSException raise:format:]。对于那些有Java背景的人来说,您应该记得Java区分了Exception和RuntimeException。Exception是一个已检查异常,RuntimeException是未检查的。特别是,Java建议对“正常错误条件”使用检查异常,对“由程序员错误引起的运行时错误”使用未检查异常。似乎Objective-C异常应该用在与未检查异常相同的地方,错误代码返回值或NSError值更适合用在要使用已检查异常的地方。

我认为你永远不应该使用异常来控制正常的程序流程。但是,当某些值与所需值不匹配时,应该抛出异常。

例如,如果某个函数接受一个值,并且该值永远不允许为nil,那么就可以抛出一个异常,而不是尝试做一些“聪明”的事情……

里斯

自从ObjC 2.0以来,Objective-C异常不再是C的setjmp() longjmp()的包装器,并且与c++异常兼容,@try是“免费的”,但抛出和捕获异常的代价要高得多。

无论如何,断言(使用NSAssert和NSCAssert宏家族)抛出NSException,这是明智的使用它们作为Ries状态。

在objective C中没有理由不正常地使用异常,甚至是表示业务规则异常。苹果可以说用NSError谁在乎呢。Obj C已经存在很长一段时间了,曾经所有的c++文档都说过同样的事情。抛出和捕获异常的代价有多大并不重要,原因是异常的生命周期非常短……这是正常流程的一个例外。我这辈子从没听人说过,那个例外需要很长时间才能被发现。

还有,有些人认为objective C本身太昂贵,而改用C或c++编写代码。所以说总是使用NSError是无知和偏执的。

但是这篇文章的问题还没有得到回答,抛出异常的最佳方式是什么。返回NSError的方法很明显。

So is it: [NSException raise:…@throw [[NSException alloc] initWithName.... 或者@throw [[MyCustomException…]< / p > ?

我在这里使用的选中/未选中规则与上面略有不同。

checked/unchecked之间的真正区别(此处使用java隐喻)非常重要——>您是否可以从异常中恢复。所谓恢复,我指的不仅仅是不崩溃。

所以我使用自定义异常类的@throw可恢复异常,因为 它可能会有一些应用程序的方法寻找某些类型的失败在多个 @catch块。例如,如果我的应用程序是一台ATM机,我将有一个@catch块用于 “WithdrawalRequestExceedsBalanceException”。< / p > 我使用NSException:raise用于运行时异常,因为我没有办法从异常中恢复, 除了在更高的级别捕获它并记录它。而且没有必要为此创建一个自定义类

无论如何,这就是我所做的,但如果有更好的,类似的表达方式,我也想知道。在我自己的代码,因为我停止编码C海拉很久以前,我从来没有返回一个NSError,即使我通过一个API传递一个。

使用NSError来传达失败而不是异常。

关于NSError的一些要点:

  • NSError允许C风格的错误代码(整数)清楚地识别根本原因,并希望允许错误处理程序克服错误。你可以很容易地在NSError实例中包装来自C库(如SQLite)的错误代码。

  • NSError还具有作为对象的好处,并提供了一种方法来更详细地描述其userInfo字典成员的错误。

  • 但最重要的是,NSError不能被抛出,因此它鼓励了一种更主动的错误处理方法,与其他语言相反,这些语言只是简单地将烫手山芋扔到调用堆栈的越来越高的位置,此时它只能报告给用户,而不能以任何有意义的方式进行处理(如果你相信遵循OOP的最大信息隐藏原则的话)。

参考链接:参考

可以使用两种方法在try catch块中引发异常

@throw[NSException exceptionWithName];

或者第二种方法

NSException e;
[e raise];

这是我从“大书呆子牧场指南(第四版)”中学到的:

@throw [NSException exceptionWithName:@"Something is not right exception"
reason:@"Can't perform this operation because of this or that"
userInfo:nil];

只有在发现自己处于指示编程错误的情况下,并希望停止应用程序运行时,才应该抛出异常。因此,抛出异常的最好方法是使用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


}