9

我的一位朋友要求我不要在 iPhone 应用程序中使用 NSException。他给出的原因是“性能瓶颈”。但我不相信它。

有人可以确认我应该限制在 iPhone 应用程序中使用 NSException 吗?如果你有使用 NSException 的最佳实践,也请提供。

更新:

链接要求我们在应用程序级别使用异常处理。有人做过吗?请给出它的优点以及它可能产生的任何其他性能障碍。

4

3 回答 3

31

简而言之:

不要使用异常来指示除不可恢复的错误之外的任何内容

只适合使用@try/@catch 处理不可恢复的错误。在 iOS 或 Mac OS X 上使用 @throw/@try/@catch 来执行类似控制流的操作是不合适的。即便如此,请仔细考虑是否最好使用异常来指示不可恢复的错误或只是崩溃(调用中止());崩溃通常会留下更多的证据。

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

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


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

当然。

系统框架使用一种设计,任何异常都被认为是致命的、不可恢复的错误;出于所有意图和目的,程序员错误。这条规则有非常有限的例外(呵呵)。

因此,在它们的实现中,如果抛出通过系统框架代码的异常,系统框架将无法确保所有内容都必须正确清理。当然,根据定义,异常是不可恢复的,为什么要支付清理费用?

考虑这个调用堆栈:

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

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

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

或者,更强烈地说:您不能your-code-2期望您可以在your-code-1.

于 2010-11-30T07:59:28.577 回答
5

我已经为我相当密集的音频应用程序使用了异常处理,没有任何问题。经过大量阅读和一些基准测试和反汇编分析,我得出了一个有争议的结论,即没有真正的理由不(智能地)使用它们,并且有充分的理由不使用它们(NSError 指针指针,无尽的条件......呸!)。人们在论坛上所说的大部分内容只是重复 Apple Docs。

我在这篇博文中进行了相当多的详细介绍,但我将在这里概述我的发现:

误解 1:@try/@catch/@finally 太贵了(就 CPU 而言)

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

误区 2:@try 块在 32 位 iOS 上是有代价的

Apple 文档谈到“ 64 位上的零成本@try 块”并声明 32 位会产生成本。一些基准测试和反汇编分析似乎表明在 32 位 iOS(ARM 处理器)上也有零成本的 @try 块。苹果的意思是说 32 位英特尔吗?

误区 3:通过 Cocoa 框架抛出的异常是未定义的,这很重要

是的,它们是“未定义的”,但是你在通过 Apple 框架做什么?当然, 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;
}
// ...

在这种情况下,异常将“通过可可框架”(主线程的运行循环)错过您的捕获并崩溃。您可以使用 GCD 轻松解决此问题dispatch_synch,并将方法调用和任何异常处理放入它的块参数中。

为什么在 NSError 上使用 NSException

任何曾在 Core Audio 等较旧的基于 C 的框架中工作过的人都知道检查、处理和报告错误是多么繁琐。@try/@catch 和 NSExceptions 提供的主要好处是使您的代码更清洁和更易于维护。

假设您有 5 行代码可以处理一个文件。每个都可能抛出一个,比如说,3 个不同的错误(例如,磁盘空间不足,读取错误等)。不是将每一行包装在一个检查 NO 返回值然后将 NSError 指针调查外包给另一个 ObjC 方法(或更糟,使用#define宏!)的条件中,而是将所有 5 行包装在一个@try 中并处理每个错误在那里。想想你会节省的线条!

通过创建 NSException 子类,您还可以轻松地集中错误消息,并避免让您的代码乱七八糟。您还可以轻松地将应用程序的“非致命”异常与致命的程序员错误(如 NSAssert)区分开来。您还可以避免需要“名称”常量(子类的名称,即“名称”)。

所有这些示例以及有关基准测试和反汇编的更多详细信息都在这篇博文中......

异常加上 try/catch/finally 是几乎所有其他主要语言(C++、Java、PHP、Ruby、Python)使用的范例。也许是时候放下偏执并接受它了……至少在 iOS 中是这样。

于 2012-11-10T12:16:30.770 回答
0

通常问问自己,你是想发出错误信号,还是真的有异常情况?如果是前者,那么无论任何性能问题,无论是真实的还是感知的,这都是一个非常糟糕的主意。如果是后者,那绝对是正确的做法。

于 2010-11-30T04:36:21.570 回答