【问题标题】:throwing an exception in objective-c/cocoa在objective-c/cocoa中抛出异常
【发布时间】:2010-09-24 08:50:56
【问题描述】:

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

【问题讨论】:

  • 不要抛出NSException,Swift 抓不到它(至少不是没有解决方法),轻松see how to create NSError(并使用@throw

标签: objective-c cocoa exception-handling


【解决方案1】:

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

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

【讨论】:

  • 我更喜欢这种方式,而不是@throw([NSException exceptionWith…]) 方式,因为它更简洁。
  • 请务必阅读关于危害的重要警告 (stackoverflow.com/questions/324284/324805#324805)
  • 我通常也更喜欢这个,但有一个问题。可能只是我当前版本的 Xcode,但是 [NSException raise...] 语法似乎没有被解析器识别为返回值的方法的退出路径。使用此语法时,我看到警告“控件可能到达非无效函数的结尾”,但使用 @throw([NSException exceptionWith...]) 语法时,解析器将其识别为退出并且不显示警告。
  • @mpstx 出于您给出的原因,我总是使用 throw 语法(这在两年后的 Xcode 4.6 中仍然相关,并且可能永远如此)。如果您想避免警告,让 IDE 认识到抛出异常是函数退出点通常很重要。
  • FWIW 我注意到@try/@catch 块也会导致“控制到达非无效函数结束”警告的假阴性(即警告不应该显示是)
【解决方案2】:

这里要注意一点。在 Objective-C 中,与许多类似的语言不同,您通常应该尽量避免对正常操作中可能出现的常见错误情况使用异常。

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

Apple's conceptual Exception handling documentation 解释相同,但用更多的词:“重要提示:您应该保留使用异常进行编程或意外运行时错误,例如越界集合访问、尝试改变不可变对象、发送无效消息,并失去与窗口服务器的连接。您通常在创建应用程序时而不是在运行时处理这些带有异常的错误。[.....]而不是异常,错误对象(NSError)和Cocoa 错误传递机制是在 Cocoa 应用程序中传达预期错误的推荐方式。”

部分原因是为了遵守 Objective-C 中的编程习惯(在简单的情况下使用返回值,在更复杂的情况下使用引用参数(通常是 NSError 类)),部分原因是抛出和捕获异常很多更昂贵,最后(也是最重要的)Objective-C 异常是 C 的 setjmp() 和 longjmp() 函数的薄包装,基本上会搞乱您仔细的内存处理,请参阅this explanation

【讨论】:

  • 我认为这适用于大多数编程语言:“尽量避免对常见错误情况使用异常”。这同样适用于 Java;处理带有异常的用户输入错误(例如)是不好的做法。不仅因为资源使用,还因为代码清晰。
  • 更重要的是,Cocoa 中的异常旨在指示不可恢复的程序错误。否则会针对框架运行,并可能导致未定义的行为。详情请见stackoverflow.com/questions/3378696/iphone-try-end-try/…
  • "Java 也是如此;"不同意。您可以在 Java 中使用检查异常来处理正常的错误情况。当然你不会使用运行时异常。
  • 我更喜欢 Cocoa 方式(例外仅针对程序员错误),所以我也更喜欢在 Java 中执行此操作,但现实情况是您应该使用环境中的典型实践,错误处理的异常在 Objective-C 中就像一股难闻的气味,但在 Java 中被大量用于此目的。
  • 此评论不回复问题。也许 OP 只是想让应用程序崩溃以测试崩溃报告框架是否按预期工作。
【解决方案3】:
@throw([NSException exceptionWith…])

Xcode 将@throw 语句识别为函数退出点,就像return 语句一样。使用@throw 语法可避免您可能从[NSException raise:…] 收到的错误“Control may reach end of non-void function”警告。

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

【讨论】:

【解决方案4】:

关于[NSException raise:format:]。对于那些具有 Java 背景的人,您会记得 Java 区分 Exception 和 RuntimeException。 Exception 是已检查的异常,而 RuntimeException 是未检查的。特别是,Java 建议对“正常错误情况”使用检查异常,对“由程序员错误导致的运行时错误”使用未检查异常。似乎 Objective-C 异常应该用在与使用未检查异常相同的地方,并且在使用检查异常的地方首选错误代码返回值或 NSError 值。

【讨论】:

  • 是的,这是正确的(尽管 4 年后 :D ),创建您自己的错误类 ABCError,它从 NSError 类扩展并将其用于检查异常而不是 NSExceptions。在发生程序员错误(例如数字格式问题等意外情况)时引发 NSExceptions。
【解决方案5】:

我认为要保持一致,最好将@throw 与您自己的扩展 NSException 的类一起使用。然后你对 try catch finally 使用相同的符号:

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

Apple 在这里解释了如何抛出和处理异常: Catching Exceptions Throwing Exceptions

【讨论】:

  • 我仍然在 try 块中因运行时异常而崩溃
【解决方案6】:

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

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

【讨论】:

  • 很高兴知道!我们有一个我们不想修改的第三方库,即使是最小的错误也会引发异常。我们必须在应用程序的一个地方捕捉它们,这让我们感到畏缩,但这让我感觉好多了。
【解决方案7】:

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

关于 NSError 的快速要点:

  • NSError 允许 C 风格的错误代码(整数)清楚地识别根本原因,并希望允许错误处理程序克服错误。您可以非常轻松地将来自 SQLite 等 C 库的错误代码包装到 NSError 实例中。

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

  • 1234563向用户报告,而不是以任何有意义的方式处理(如果您相信遵循 OOP 的最大信息隐藏原则,则不会)。

参考链接:Reference

【讨论】:

  • 此评论不回复问题。也许 OP 只是想让应用程序崩溃以测试崩溃报告框架是否按预期工作。
【解决方案8】:

这是我从“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。 :)
【解决方案9】:

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

@throw[NSException exceptionWithName];

或者第二种方法

NSException e;
[e raise];

【讨论】:

    【解决方案10】:

    我相信你永远不应该使用异常来控制正常的程序流程。但是,只要某些值与所需值不匹配,就应该抛出异常。

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

    里斯

    【讨论】:

      【解决方案11】:

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

      【讨论】:

        【解决方案12】:

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

        }

        【讨论】:

          【解决方案13】:

          没有理由不在目标 C 中正常使用异常,甚至表示业务规则异常。苹果可以说使用 NSError 谁在乎。 Obj C 已经存在了很长时间,并且曾经所有的 C++ 文档都说了同样的话。抛出和捕获异常的成本有多高并不重要的原因是异常的生命周期非常短,而且......它是正常流程的一个例外。我一生中从未听过任何人说过,这个异常需要很长时间才能被抛出和捕获。

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

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

          是这样的:[NSException raise:... @throw [[NSException alloc] initWithName.... 或@throw [[MyCustomException...?

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

          checked/unchecked(这里使用 java 比喻)之间的真正区别很重要 --> 是否可以从异常中恢复。恢复我的意思不仅仅是不崩溃。

          所以我使用带有@throw 的自定义异常类来处理可恢复的异常,因为 我可能会有一些应用程序方法来查找多个特定类型的故障 @catch 块。例如,如果我的应用程序是一台 ATM 机,我将有一个 @catch 块用于 “WithdrawalRequestExceedsBalanceException”。

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

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

          【讨论】:

          • 我建议在做出诸如“没有理由不在目标 C 中正常使用异常”之类的概括性陈述之前,尝试将带有异常的服务器编程为错误案例的正常流程的一部分。信不信由你,在 ObjC 中编写高性能应用程序(或至少部分应用程序)是有原因的,而抛出异常通常会严重影响性能。
          • 确实有很好的理由为什么不在 Cocoa 中使用异常。有关更多信息,请参阅 Bill Bumgarner 的回答:stackoverflow.com/questions/3378696/iphone-try-end-try/…。他知道他在说什么(提示:检查他的雇主)。 Cocoa 中的异常被视为不可恢复的错误,并且会使系统处于不稳定状态。 NSError 是传递一般错误的方法。
          • 例外是例外。业务规则失败肯定不符合条件。 “找到并设计掉异常密集的代码可以带来不错的性能胜利。” MSDN 通过codinghorror.com/blog/2004/10/…
          • 不能从块中抛出异常。 ARC 环境中抛出的异常可能会使您的程序泄漏。因此投反对票。
          • “抛出和捕获异常的成本有多高并不重要” 我正在编写一个性能至关重要的模拟器。我不能抛出一大堆昂贵的异常。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-03-25
          • 2010-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多