11

大家好。我一直在阅读 Apple 关于何时/何地/如何使用 NSError 与 @try/@catch/@finally 的建议。从本质上讲,我的印象是 Apple 认为最好避免使用异常处理语言结构,除非作为在意外错误情况下停止程序执行的机制(也许有人可以举一个这种情况的例子?)

我来自 Java,当一个人想要处理错误时,异常是要走的路。诚然,我仍然在 Java 思想领域,但我正在慢慢掌握 NSError 所提供的一切。

我挂断的一件事是发生错误时清理内存的任务。在许多情况下(例如,使用 C、C++ 库、CoreFoundation 等),您需要在因错误而中断函数之前进行大量内存清理。

这是我编写的一个示例,它准确地反映了我遇到的情况。使用一些虚构的数据结构,该函数打开一个文件句柄并创建一个“MyFileRefInfo”对象,其中包含有关如何处理文件的信息。在关闭文件句柄并释放结构的内存之前,对文件进行了一些处理。使用Apple的建议我有这个方法:

- (BOOL)doSomeThingsWithFile:(NSURL *)filePath error:(NSError **)error
{
  MyFileReference inFile; // Lets say this is a CF struct that opens a file reference
  MyFileRefInfo *fileInfo = new MyFileRefInfo(...some init parameters...);

  OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:99 userInfo:nil];
    delete fileInfo;
    return NO;
  }

  err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  err = DoSomeOtherStuffWithTheFile(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:101 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  CloseFileHandle(inFile);
  delete fileInfo;
  return YES;

}

现在.. 我的 Java 逻辑告诉我,最好将其设置为 try/catch/finally 结构并将所有调用以关闭文件句柄并在 finally 块中释放内存。

像这样。。

    ...

    @try
    {
      OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);
      if(err != NoErr)
      {
        ... throw some exception complete with error code and description ...
      }

      err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

      if(err != NoErr)
      {
         ... throw some exception ...
      }

      ... etc ...        
}
@catch(MyException *ex)
{
        *error = [NSError errorWithDomain:@"myDomain" code:[ex errorCode] userInfo:nil];
        return NO;
}
@finally
{
        CloseFileHandle(inFile); // if we don't do this bad things happen
        delete fileInfo;
}
return YES;

我是否疯狂地认为这是一个更优雅的解决方案,冗余代码更少?我错过了什么?

4

4 回答 4

17

丹尼尔的回答是正确的,但这个问题值得一个更直率的回答。

仅在遇到不可恢复的错误时才抛出异常。

在传达可以从中恢复的错误条件时使用 NSError。

通过 Apple 框架中的框架抛出的任何异常都可能导致未定义的行为。

开发中心提供了一个异常编程主题文档。

于 2010-01-05T23:50:19.833 回答
12

从本质上讲,我的印象是 Apple 认为最好避免使用异常处理语言结构,除非作为在意外错误情况下停止程序执行的机制(也许有人可以举一个这种情况的例子?)

这不是我的印象。我认为 Apple 建议将异常用于真正的异常情况,将 NSError 用于预期的失败。由于你来自 Java,我认为 NSError -> java.lang.Exception 和 Obj-C Exceptions -> java.lang.RuntimeException。当程序员做错事时使用 Obj-C 异常(例如,错误地使用了 API),并在发生预期故障时使用 NSError(例如,找不到远程服务器)。

当然,这只是我对苹果立场的解读。另一方面,我喜欢例外!

于 2010-01-05T16:05:24.797 回答
3

Objective-C 中的异常在历史上一直是“严重的”,进入 try 块的性能成本、抛出的成本、使用 finally 的成本等等。因此,Cocoa 开发人员通常会避免“哦,不,”之外的异常。天要塌下来的那种情况——如果文件丢失,使用 NSError,但如果没有文件系统并且可用内存为负数,那是一个例外。

这就是历史观。但如果你在 10.5 或更高版本上构建 64 位应用程序,异常架构已被重写为“零成本”,这可能意味着历史视图不再相关。与几乎任何事情一样,它归结为各种因素 - 如果以一种方式工作对您来说更自然,并且会让您更快地完成,如果您没有遇到任何与性能相关的问题,如果稍微有点与“传统”Objective-C 代码不一致不会打扰您……那么没有理由不使用异常。

于 2010-01-05T15:57:50.077 回答
2

根据More iPhone 3 DevelopmentDave Mark 和 Jeff LeMarche 的说法,异常仅用于真正的异常情况,通常表明代码中存在问题。您永远不应该使用异常来报告普通的错误情况。异常在 Objective-C 中的使用频率远低于许多其他语言,例如 Java 和 C++。

当您需要捕获代码中的错误时,您可以使用异常。当用户可能需要解决问题时,您使用错误。

这是一个使用异常的示例:

我们正在编写一个超类,我们希望确保它的子类实现给定的方法。Objective-C 没有抽象类,也缺乏强制子类实现给定方法的机制。然而,我们可以使用异常立即通知我们忘记在子类中实现该方法。我们会遇到运行时异常,而不是不可预知的行为。我们可以很容易地调试它,因为我们的异常会准确地告诉我们我们做错了什么:

NSException *ex = [NSException exceptionWithName:@"Abstract Method Not Overridden" reason:NSLocalizedString(@"You MUST override the save method", @"You MUST override the save method") userInfo:nil];
[ex raise];

因为问题是程序员的错误,而不是用户可以修复的问题,所以我们使用异常。

于 2010-07-03T06:30:46.430 回答