10

在 Apple 的并发编程指南中,NSOperation 子类示例(非并发和并发品种)使用异常处理,我想知道为什么他们在操作中鼓励这种风格。

清单 2-4 响应取消请求

- (void)main {
   @try {
      BOOL isDone = NO;

      while (![self isCancelled] && !isDone) {
          // Do some work and set isDone to YES when finished
      }
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}

我的理解是,异常处理通常不是 Objective-C 代码中的常见做法——异常本质上是程序员错误,应该导致应用程序崩溃,而意外输入最好由 NSError 处理。(我可能误解的理解来自这样的事情这个

我想知道 NSOperations 是否存在异常处理很重要的特定情况,或者这是否是该指南特定作者的首选风格。

附带说明一下,一些 NSOperation 示例代码遵循这种风格,而其他示例则没有。大多数高可见性 OSS 不使用异常(例如 AFNetworking)。

4

2 回答 2

9

您的理解是正确的 - 应该使用 NSError (或类似的)来传达错误信息,而不是异常。大多数 Objective-C 代码都不是异常安全的,至少会泄漏资源。作为一般规则,永远不要让您的代码将异常泄漏到其他任何人的代码中——无论是 Apple 的还是第三方的。一些第 3 方框架可能会明确表明它们是异常安全的,但这种情况很少见。

通过该原则,您可以了解为什么无论如何都应该在main方法中使用包罗万象的异常处理程序。但实际上还有另一个原因:您的操作将在专用线程上运行。从您的操作中抛出的异常将向上传播,但不会进一步传播。操作的逻辑调用者或所有者不会得到它们,因为它们在不同的线程上运行(或根本不运行)。所以泄露的异常要么会杀死你的整个程序,要么会被默默吞下而没有其他迹象。然后您的程序可能会卡在一个奇怪的状态 - 因为您没有意识到发生了错误,您可能会继续等待永远不会到达的操作结果。

此外,Apple 在并发编程指南中有一节讨论了处理错误和异常。他们关于“离散实体”的第一点是暗指我在上一段中所说的:

处理错误和异常

因为操作本质上是应用程序中的离散实体,所以它们负责处理出现的任何错误或异常。在 OS X v10.6 及更高版本中, NSOperation类提供的默认启动方法不会捕获异常。(在 OS X v10.5 中,start 方法确实会捕获和抑制异常。)您自己的代码应该始终直接捕获和抑制异常。它还应该检查错误代码并根据需要通知应用程序的适当部分。如果你替换了 start 方法,你必须同样在你的自定义实现中捕获任何异常,以防止它们离开底层线程的范围。

您应该准备处理的错误情况类型如下:

  • 检查和处理 UNIX errno 样式的错误代码。
  • 检查方法和函数返回的显式错误代码。
  • 捕获您自己的代码或其他系统框架抛出的异常。
  • 捕获 NSOperation 类本身抛出的异常,在以下情况下会抛出异常:
    • 当操作尚未准备好执行但调用了它的 start 方法时
    • 当操作正在执行或完成时(可能是因为它被取消了)并且它的 start 方法被再次调用
    • 当您尝试将完成块添加到已执行或已完成的操作时
    • 当您尝试检索已取消的NSInvocationOperation对象的结果时

如果您的自定义代码确实遇到异常或错误,您应该采取任何必要的步骤将该错误传播到应用程序的其余部分。NSOperation 类不提供将错误结果代码或异常传递给应用程序其他部分的显式方法。因此,如果此类信息对您的应用程序很重要,您必须提供必要的代码。

于 2012-12-02T17:45:46.230 回答
3

我认为这篇文章和随附的答案很好地阐述了一般异常与无异常处理主题!

在资源不是自动管理的情况下抛出异常是不安全的。Cocoa 框架(和相邻框架)就是这种情况,因为它们使用手动引用计数。

如果抛出异常,通过展开堆栈跳过的任何释放调用都将导致泄漏。仅当您确定不会恢复时,这应该限制您投掷,因为当进程退出时所有资源都返回给操作系统。

不幸的是,NSRunLoops 倾向于捕获传播给它们的所有异常,所以如果你在一个事件期间抛出,你将继续到下一个事件。这显然是非常糟糕的。因此,最好不要扔。

如果您使用垃圾收集的 Objective-C,这个问题就会减少,因为任何由 Objective-C 对象表示的资源都将被正确释放。但是,未包装在 Objective-C 对象中的 C 资源(例如文件描述符或 malloc 分配的内存)仍然会泄漏。

所以,总而言之,不要扔。

正如您所提到的,Cocoa API 对此有几种解决方法。返回 nil 和 NSError** 模式是其中的两个。

于 2012-12-02T16:41:01.960 回答