在objective-c / cocoa中抛出异常

throwing an exception in objective-c/cocoa

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


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

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


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

Apple针对obj-c 2.0的文档指出:"重要事项:异常在objective-c中是资源密集型的。您不应将异常用于一般的流控制,也不应仅仅表示错误(例如文件不可访问)。

苹果公司的概念性异常处理文档也解释了这一点,但用了更多的话:"重要的是:您应该保留对编程或意外运行时错误(如越界集合访问)、尝试改变不可变对象、发送无效消息以及断开与窗口服务器的连接)的异常使用。通常在创建应用程序而不是在运行时处理这些类型的错误,但有例外。[……]建议使用错误对象(nserrror)和cocoa错误传递机制来传递cocoa应用程序中的预期错误,而不是异常。"

之所以这样做,部分原因是为了遵守Objective-C中的编程习惯(在简单的情况下使用返回值,在更复杂的情况下使用引用参数(通常是nserrror类),部分原因是抛出和捕获异常的成本要高得多,最后(最重要的是,还很困惑)Objective-C异常是一个薄包装。r关于C的setjmp()和longjmp()函数,本质上是破坏了您小心的内存处理,请参阅这个解释。


1
@throw([NSException exceptionWith…])


我没有代表来评论艾杰姆斯的反应,所以我想我需要把我的放在这里。对于来自Java背景的人,您会记得Java区分异常和RunTimeExtExchange。异常是选中的异常,运行时异常未选中。特别是,Java建议使用"正常错误条件"的检查异常和"程序员错误导致的运行时错误"的未检查异常。"Objy-C异常"似乎应该在您使用未检查异常的相同位置使用,而错误代码返回值或NSerror值在您所在的位置是首选的。将使用选中的异常。


我认为在扩展nsException的类中使用@throw更好。然后,您将使用相同的符号来尝试catch finally:

1
2
3
4
5
6
7
8
9
@try {
.....
}
@catch{
...
}
@finally{
...
}

苹果在这里解释了如何抛出和处理异常:捕获异常避免抛出异常


自从Objc 2以来,Objto-C异常不再是C的SETJMP()LangJMP()的包装器,并且与C++异常兼容,@尝试是"免费的",但是抛出和捕获异常更加昂贵。

不管怎样,断言(使用nsassert和nsassert宏家族)会抛出nsexception,并且明智地将它们用作RIES状态。


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

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

使用nserrror来通信失败,而不是异常。

有关nserrror的要点:

  • nserrror允许C样式的错误代码(整数)清楚地识别根本原因,并希望允许错误处理程序克服错误。您可以非常容易地在nserrror实例中包装C库(如sqlite)中的错误代码。

  • nserrror还具有作为对象的好处,并提供了一种方法,通过它的userinfo字典成员更详细地描述错误。

  • 但最重要的是,nserrror不能被抛出,因此它鼓励采用更积极主动的错误处理方法,与其他语言相比,nserrror只会将烫手山芋进一步抛向调用堆栈,此时它只能向用户报告,而不能以任何有意义的方式进行处理(如果您相信遵循oop的最大原则,则不会这样做)。隐藏的信息。

参考链接:参考


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

1
@throw[NSException exceptionWithName];

或者第二种方法

1
2
NSException e;
[e raise];

我相信你不应该用异常来控制正常的程序流。但当某个值与所需的值不匹配时,应该抛出异常。

例如,如果某个函数接受了一个值,并且该值永远不允许为零,那么可以特洛伊一个异常,而不是尝试做一些"智能"的事情……

里斯


case的示例代码:@throw([nsException exceptionwithname:…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (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) {

    &nbsp;&nbsp;NSLog( @"Caught Exception Name: %@", exception.name);
    &nbsp;&nbsp;NSLog( @"Caught Exception Reason: %@", exception.reason );

    resultString = exception.reason;

} @finally {

    completionBlock(resultString);
}

}

使用:

1
2
3
[self parseError:error completionBlock:^(NSString *error) {
            NSLog(@"%@", error);
        }];

另一个更高级的用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
- (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

}

}


只有当您发现自己处于指示编程错误并希望停止应用程序运行的情况下,才应该抛出异常。因此,抛出异常的最佳方法是使用nsassert和nsparameteassert宏,并确保未定义ns_块_断言。


没有理由不在目标C中正常使用异常,甚至表示业务规则异常。苹果可以说使用N箭头谁在乎。Objc已经有很长时间了,而且所有的C++文档都曾说过同样的话。不管抛出和捕获异常的代价有多高,原因是异常的生命周期非常短,而且……这是正常流的异常。在我的生活中,我从来没有听过任何人说,那个例外的人花了很长时间才被抛出和抓住。

此外,有人认为目标C本身太昂贵,而C或C++中的代码则相反。所以说总是使用nserrror是不明智和偏执的。

但是这个线程的问题还没有得到回答,什么是抛出异常的最佳方法。返回nserrror的方法很明显。

所以是这样的:【N例外情况提出:…@抛出[[nsException alloc]initWithName…。或@throw[[mycustomException…?

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

(使用Java隐喻)检查/未检查之间的实际差异很重要——>您是否可以从异常中恢复。我所说的恢复不仅仅是指撞车。

所以我使用带有@throw的自定义异常类来处理可恢复的异常,因为很可能我会有一些应用程序方法在多个应用程序中查找某些类型的失败@接住挡块。例如,如果我的应用程序是一台ATM机,我会为"提款请求超出余额异常"。

我使用nsException:raise for runtime异常,因为我无法从异常中恢复,除了在更高的级别捕获并记录它。为它创建自定义类是没有意义的。

不管怎样,这就是我要做的,但是如果有更好的,同样有表现力的方式,我也想知道。在我自己的代码中,由于很久以前我就停止了对C的编码,所以即使通过API传递了一个nserrror,我也不会返回nserrror。