iPhone应用中NSException的使用

11

我的一个朋友告诉我在iPhone应用程序中不要使用NSException。他给出的原因是“性能瓶颈”。但我并不信服。

有人可以证实我们应该避免在iPhone应用程序中使用NSException吗?如果您有使用NSException的最佳实践,请提供。

更新:

这个链接要求我们在应用程序级别使用异常处理。有人做过吗?请给出其优点以及可能带来的任何其他性能问题。

3个回答

33

简言之:

不要使用异常来表示除不可恢复的错误以外的任何情况

只有在处理不可恢复的错误时使用@try/@catch才是适当的做法。在iOS或Mac OS X上,从未使用@throw/@try/@catch进行流程控制都是合适的。即使如此,在指示不可恢复的错误还是仅仅崩溃(调用abort())中仔细考虑一下; 崩溃通常会留下更多证据。

例如,不能使用捕获越界异常的方法,除非您的目标是捕获它们并以某种方式报告错误,然后 - 通常 - 崩溃或者至少警告用户你的应用处于不一致状态且可能会丢失数据。

通过系统框架代码抛出的异常的行为是未定义的。


你能详细解释一下“通过系统框架代码抛出的异常的行为是未定义的”吗?

可以。

系统框架采用这样的设计:任何异常都被视为致命的、不可恢复的错误; 就实际意义而言,是一个程序员错误。除此之外,这个规则有非常有限的例外。

因此,在它们的实现中,如果抛出通过系统框架代码传递的异常,系统框架不会确保所有东西都被适当地清理。既然异常是不可恢复的,为什么要支付清理成本呢?

考虑这个调用堆栈:

your-code-1()
    system-code()
        your-code-2()

即,您的代码调用系统代码,系统代码再调用您的更多代码(这是一种非常常见的模式,尽管调用堆栈显然更深)。

如果your-code-2抛出异常,则异常传递到system-code意味着行为未定义;system-code可能会或可能不会使您的应用程序处于未定义的、可能崩溃或丢失数据的状态。

或者更强烈地说:您无法在your-code-2中抛出异常,并期望在your-code-1中捕获和处理它。


你能详细解释一下“通过系统框架代码抛出的任何异常的行为是未定义的”吗? - RK-
+1,这个不错。请问我的异常假设不正确吗?如果我在特定异常情况下使用NSException(比如数组越界),那么它会加载所有类来验证异常。请回复。 - Ishu

5
我已经在我的相当复杂的音频应用中使用了异常处理,没有任何问题。经过大量阅读、一些基准测试和反汇编分析,我得出了一个有争议的结论:没有真正的理由不使用它们(智能地),但有很多理由不使用NSError指针指针、无限制的条件语句等等。大部分人在论坛上所说的都是重复苹果文档的内容。
我在这篇博客文章中详细介绍了我的发现:

谬误 1:@try/@catch/@finally 太昂贵(从CPU的角度来看)

在我的iPhone 4上,抛出和捕获100万个异常大约需要8.5秒钟。这相当于每个异常只需要约8.5微秒。在您的实时CoreAudio线程中昂贵吗?也许有点(但您永远不会在那里抛出异常,对吗?),但在UIAlert告诉用户存在打开文件问题时,8.5μs的延迟会被注意到吗?

神话2:@try块在32位iOS上有成本

苹果文档提到“64位上的零成本@try块”,并指出32位会产生成本。一些基准测试和反汇编分析似乎表明,在32位iOS(ARM处理器)上也有零成本的@try块。苹果是不是想说32位的Intel呢?

神话3:通过Cocoa Frameworks抛出的异常未定义很重要

是的,它们是“未定义的”,但是你为什么要通过Apple框架抛出它们呢?当然,苹果不会替你处理它们。实现可恢复错误的异常处理的整个重点是在本地处理它们,而不是每一行都在“本地”处理。

这里的一个边缘情况是像NSObject:performSelectorOnMainThread:waitUntilDone:这样的方法。如果后面的参数是YES,这就像一个同步函数,这种情况下你可能会期望异常冒泡到你的调用范围。例如:

/////////////////////////////////////////////////////////////////////////
#pragma mark - l5CCThread
/////////////////////////////////////////////////////////////////////////

@interface l5CCThread : NSThread @end

@implementation l5CCThread

- (void)main
{
    @try {
        
        [self performSelectorOnMainThread:@selector(_throwsAnException) withObject:nil waitUntilDone:YES];
        
    } @catch (NSException *e) {
        NSLog(@"Exception caught!");
    }
}
- (void)_throwsAnException { @throw [NSException exceptionWithName:@"Exception" reason:@"" userInfo:nil]; }

@end

/////////////////////////////////////////////////////////////////////////
#pragma mark - l5CCAppDelegate
/////////////////////////////////////////////////////////////////////////

@implementation l5CCAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    l5CCThread *thd = [[l5CCThread alloc] init];
    [thd start];
    
    return YES;
}
// ...

在这种情况下,异常会通过Cocoa框架(主线程的运行循环)传递,绕过你的catch并导致崩溃。您可以轻松使用GCD的dispatch_synch解决此问题,并将方法调用及任何异常处理放入其块参数中。
为什么要使用NSException而不是NSError 任何曾经在诸如Core Audio之类的旧版基于C的框架中工作的人都知道检查、处理和报告错误是多么繁琐。@try/@catch和NSExceptions提供的主要好处是使您的代码更清洁、更易于维护。
假设您有5行代码用于处理文件。每一行可能会抛出三种不同的错误之一(例如磁盘空间不足、读取错误等)。您可以将所有5行代码包装在一个单独的@try中,并在那里处理每个错误。想象一下您可以省下的代码行数!
通过创建NSException子类,您还可以轻松地集中错误消息,并避免让代码散布这些消息。您还可以轻松区分应用程序的“非致命”异常和致命的程序员错误(如NSAssert)。您还可以避免需要“名称”常量(子类的名称即为“名称”)。
有关所有这些示例以及更多详细信息和反汇编的内容,请参见此博客文章...
除了iOS之外,几乎所有其他主要语言(C ++,Java,PHP,Ruby,Python)都使用异常加try/catch/finally范式。也许现在是放弃偏执并接受它的时候了...至少在iOS上是这样。

8
暗示可在可恢复的错误和流程控制中使用NSExceptions是不正确的。框架明确不支持这种用法。 - bbum
除了bbum提出的(更重要的)观点之外,我很难看出try/catch如何提高可读性:如果你只想不恢复,那就尽早返回。如果你有一系列的catches,那么这样做有什么好处呢? - danyowdee
2
因为你将每种错误类型的处理集中到给定代码块的单个位置,而不是每行处理。 - Hari Honor

0

通常要问自己的是,你是在试图发出错误信号,还是实际上存在异常情况?如果是前者,那么无论任何性能问题,真实或感知到的,都是非常糟糕的想法。如果是后者,那么这绝对是正确的做法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接