在Objective-C /可可中引发异常


Answers:


528

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

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];

9
我更喜欢这种方式,@throw([NSException exceptionWith…])因为它更简洁。
山姆·索菲斯

9
请务必阅读从危害的重要的警告(stackoverflow.com/questions/324284/324805#324805
e.James

26
我通常也喜欢这种方式,但是有一个陷阱。可能只是我当前的Xcode版本,但是[NSException提高...]语法似乎没有被解析器识别为返回值的方法的退出路径。使用此语法时,我看到警告“控件可能会到达非void函数的结尾”,但是使用@throw([NSException exceptionWith…])语法时,解析器将其识别为退出,并且不显示警告。
mattorb

1
@mpstx出于您所给出的原因,我始终使用throw语法(两年后在Xcode 4.6中仍然有意义,并且可能一直如此)。如果要避免警告,让IDE认识到引发异常是函数出口点经常很重要。
Mark Amery

FWIW我注意到@ try / @ catch块还会导致错误的“控制到达非空函数结束”警告(例如,警告应该在适当的时候不显示)的假阴性结果
Brian Gerstle

256

请注意这里。与许多类似的语言不同,在Objective-C中,您通常应避免对正常操作中可能发生的常见错误情况使用异常。

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

Apple的概念性异常处理文档对此进行了解释,但使用了更多的词语:“重要:您应保留将异常用于编程或意外的运行时错误,例如越界访问集合,尝试使不可变对象发生突变,发送无效消息等通常会在创建应用程序时而不是在运行时处理这些带有异常的错误,这些错误带有异常,而不是异常,而是错误对象(NSError)和可可错误传递机制是在可可应用程序中传达预期错误的推荐方法。”

这样做的原因部分是为了遵守Objective-C中的编程习惯用法(在简单情况下使用返回值,在更复杂情况下使用by-reference参数(通常是NSError类)),部分原因是抛出和捕获异常会更加昂贵并且最后(也是最重要的一点),Objective-C异常是对C的setjmp()和longjmp()函数的一个简单包装,本质上搞乱了您仔细的内存处理,请参阅以下说明


11
我认为这适用于大多数编程语言:“尽量避免在常见错误情况下使用异常”。Java中也是如此。处理例外情况下的用户输入错误(例如)是一种不好的做法。不仅因为资源使用,而且因为代码清晰。
beetstra 2010年

6
更重要的是,可可中的异常旨在指示不可恢复的程序错误。否则会违反该框架,并且可能导致不确定的行为。有关详细信息,请参见stackoverflow.com/questions/3378696/iphone-try-end-try/…
KPM 2012年

9
“ Java也是如此;” 不同意。您可以在Java中使用已检查的异常,这对于正常的错误情况而言还不错。当然,您不会使用运行时异常。
Daniel Ryan

我更喜欢Cocoa方式(例外仅适用于程序员错误),所以我也更喜欢用Java进行,但现实情况是,您应该在环境中采用典型做法,并且错误处理的异常像在Objective-C中有难闻的气味,但是在Java中为此经常使用。
gnasher729 2014年

1
此评论未回答问题。也许OP只是想让应用程序崩溃以测试崩溃报告框架是否按预期工作。
西蒙(Simon)

62
@throw([NSException exceptionWith…])

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

另外,@throw可用于抛出非NSException类的对象。


11
@Steph Thirion:有关所有详细信息,请参见developer.apple.com/documentation/Cocoa/Conceptual/Exceptions/…。底线?两者都可以,但是@throw可以用来抛出非NSException类的对象。
e.James

33

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


1
是的,这是正确的(尽管经过了:D 4年),创建您自己的错误类ABCError,该类从NSError类扩展,并将其用于检查的异常而不是NSExceptions。引发NSExceptions,其中发生程序员错误(意外情况,例如数字格式问题)。
chathuram

15

我认为要保持一致,最好将@throw与扩展NSException的自己的类一起使用。然后,您最终使用相同的符号进行尝试捕获:

@try {
.....
}
@catch{
...
}
@finally{
...
}

Apple在这里说明了如何引发和处理异常:捕获异常 引发 异常


我仍然在尝试块中因运行时异常而崩溃
famfamfam

14

从ObjC 2.0开始,Objective-C异常不再是C的setjmp()longjmp()的包装,并且与C ++异常兼容,@ try是“免费的”,但是抛出和捕获异常的代价更高。

无论如何,断言(使用NSAssert和NSCAssert宏系列)会抛出NSException,并且理智地将它们用作Ries状态。


很高兴知道!我们有一个我们不想修改的第三方库,即使是最小的错误也会引发异常。我们必须在应用程序中将它们捕获到一个位置,这只会使我们畏缩,但这会让我感觉好一些。
尤里·布兰吉斯

8

使用NSError传达失败而不是例外。

关于NSError的简要要点:

  • NSError允许C样式错误代码(整数)清楚地标识根本原因,并希望允许错误处理程序克服错误。您可以非常轻松地将来自诸如SQLite之类的C库的错误代码包装在NSError实例中。

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

  • 但最重要的是,不能抛出NSError,因此它鼓励采用更主动的错误处理方法,而与其他语言相比,其他语言只是将热点土豆扔到越来越远的调用堆栈中,此时只能将其报告给用户,并且不会以任何有意义的方式进行处理(如果您相信遵循OOP的最大信息隐藏原则,那就不会)。

参考链接: 参考


此评论未回答问题。也许OP只是想让应用程序崩溃以测试崩溃报告框架是否按预期工作。
西蒙(Simon)

7

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

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

可以,但是并不能说明太多userInfo:nil。:)
心教堂

6

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

@throw[NSException exceptionWithName];

或第二种方法

NSException e;
[e raise];

3

我相信您永远不要使用异常来控制正常的程序流。但是,只要某个值与期望值不匹配,就应该引发异常。

例如,如果某个函数接受一个值,并且永远不允许该值为nil,那么抛出异常而不是尝试执行“智能”操作就可以了...

里斯


0

仅当发现自己处于指示编程错误的状况并且想要停止应用程序运行时,才应该引发异常。因此,引发异常的最佳方法是使用NSAssert和NSParameterAssert宏,并确保未定义NS_BLOCK_ASSERTIONS。


0

案例的示例代码:@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

}

}


-7

没有理由不在目标C中正常使用异常,甚至不表示业务规则异常。苹果可以说使用NSError谁在乎。Obj C已经存在很长时间了,一次所有C ++文档都说了同样的话。引发和捕获异常的代价无关紧要的原因是,异常的生存期非常短,并且...异常超出了正常流程。我从来没有听过有人说过这样的话,那个人花了很长时间才被抛出并抓住。

而且,有些人认为目标C本身太昂贵了,而是用C或C ++编写代码。所以说总是使用NSError是不明智和偏执的。

但是尚未解决此线程的问题是引发异常的最佳方法是什么。返回NSError的方法很明显。

就是这样:[NSException提高:... @throw [[NSException分配] initWithName ....或@throw [[MyCustomException ...?

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

(这里使用java隐喻)选中/未选中之间的真正区别很重要->是否可以从异常中恢复。通过恢复,我的意思是不仅不会崩溃。

因此,我将自定义异常类与@throw一起用于可恢复的异常,因为我可能会使用一些app方法在多个@catch块中查找某些类型的故障。例如,如果我的应用程序是ATM机,则对于“ WithdrawalRequestExceedsBalanceException”,我将有一个@catch块。

我将NSException:raise用于运行时异常,因为我无法从异常中恢复,只能在更高级别捕获并记录它。为此没有必要创建自定义类。

无论如何,这就是我的工作,但如果我有更好的,类似的表达方式,我也想知道。在我自己的代码中,因为很久以前我停止对C进行编码,所以即使我通过API传递了NSError,也永远不会返回NSError。


4
我建议尝试在异常情况的正常流程中对带有异常的服务器进行编程,然后再进行诸如“没有理由不在目标C中正常使用异常”这样的概括性声明。信不信由你,有理由在ObjC中编写高性能应用程序(或至少部分应用程序),并且抛出异常通常会严重影响性能。
jbenet

6
确实有很好的理由为什么不在可可粉中使用例外。有关更多信息,请参见Bill Bumgarner的答案:stackoverflow.com/questions/3378696/iphone-try-end-try/…。他知道他在说什么(提示:检查他的雇主)。可可中的异常被视为不可恢复的错误,可能会使系统处于不稳定状态。NSError是传递一般错误的方法。
布拉德·拉尔森

例外是例外。业务规则失败肯定不符合条件。“查找和设计出大量的异常代码可以取得不错的性能。” MSDN通过codinghorror.com/blog/2004/10/...
乔纳森Watmough

3
不能从块引发异常。在ARC环境中引发的异常可能会使程序泄漏。因此,下降。
Moszi 2012年

“不管抛出和捕获异常有多昂贵,”我正在编写对性能至关重要的仿真器。我不能抛出一大堆昂贵的异常。
NobodyNada 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.