为啥 NSOperation 示例代码使用@try & @catch
Posted
技术标签:
【中文标题】为啥 NSOperation 示例代码使用@try & @catch【英文标题】:Why does NSOperation example code uses @try & @catch为什么 NSOperation 示例代码使用@try & @catch 【发布时间】:2012-12-02 16:06:47 【问题描述】:在 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 处理。 (我可能误解的理解来自this 和this 之类的东西)
我想知道 NSOperations 是否存在异常处理很重要的特定情况,或者这是否是该指南特定作者的首选风格。
附带说明,一些 NSOperation 示例代码遵循这种风格,而其他示例则没有。大多数高可见性 OSS 不使用异常(例如 AFNetworking)。
【问题讨论】:
【参考方案1】:您的理解是正确的 - 应该使用 NSError(或类似的)来传达错误信息,而不是异常。大多数 Objective-C 代码都不是异常安全的,至少会泄漏资源。作为一般规则,永远不要让您的代码将异常泄漏到其他任何人的代码中——无论是 Apple 的还是第三方的。一些第 3 方框架可能会明确表明它们是异常安全的,但这种情况很少见。
通过该原则,您可以了解为什么无论如何都应该在您的main
方法中拥有一个包罗万象的异常处理程序。但实际上还有另一个原因:您的操作将在专用线程上运行。从您的操作中抛出的异常将向上传播,但不会进一步传播。操作的逻辑调用者或所有者不会得到它们,因为它们在不同的线程上运行(或根本不运行)。所以泄露的异常要么会杀死你的整个程序,要么会被默默吞下而没有其他迹象。然后您的程序可能会卡在一个奇怪的状态 - 因为您没有意识到发生了错误,您可能会继续等待永远不会到达的操作结果。
此外,Apple 在Concurrency Programming Guide 中有一个部分讨论Handling Errors and Exceptions。他们关于“离散实体”的第一点是暗指我在上一段中所说的:
处理错误和异常
因为操作本质上是 应用程序中的离散实体,它们负责 处理出现的任何错误或异常。在 OS X v10.6 及更高版本中, NSOperation 类提供的默认启动方法没有 捕捉异常。 (在 OS X v10.5 中, start 方法确实会捕获并 抑制异常。)您自己的代码应始终捕获并抑制 直接例外。它还应该检查错误代码并通知 根据需要应用程序的适当部分。如果你更换 start 方法,您必须类似地捕获您的任何异常 自定义实现以防止它们离开 底层线程。
在您应该准备处理的错误情况类型中 如下:
检查和处理 UNIX errno 样式的错误代码。 检查显式错误 方法和函数返回的代码。 捕获由引发的异常 您自己的代码或其他系统框架。 捕获抛出的异常 由 NSOperation 类本身,它在 以下情况: 当操作尚未准备好执行但 它的启动方法被调用 当操作正在执行或完成时 (可能是因为被取消了)并且调用了它的start方法 再次 当您尝试将完成块添加到正在执行的操作时 已经执行或完成 当您尝试检索结果时 已取消的 NSInvocationOperation 对象如果您的自定义代码 确实遇到异常或错误,您应该采取任何措施 需要将该错误传播到应用程序的其余部分。 NSOperation 类没有提供显式的传递方法 沿着错误结果代码或异常到您的其他部分 应用。因此,如果此类信息对您很重要 应用程序,您必须提供必要的代码。
【讨论】:
也许我应该更明确地说明main
中的异常处理程序是防御性编程:即使您的代码没有故意引发异常,拥有该处理程序也会让您放心您最终调用什么代码,或者将来会发生什么,您将是安全的。这与您应该始终在异常处理程序中包装锁定/解锁函数的原因相同,以确保您不会让锁处于锁定状态并最终导致死锁(或者在特定的锁情况下使用 @synchronize,它会为您做到这一点)。
韦德,过去几天我一直在思考这个问题,但仍然无法理解你的答案。如果我有一个编程错误(例如除以零),我希望我的线程(因此应用程序,对吗?)崩溃。具体来说,我不明白以下内容:“大多数 Objective-C 代码不是异常安全的,至少会泄漏资源”(您的意思是在引发异常后泄漏内存?如何?)以及来自 Apple 的指南“您自己的代码应该总是直接捕获和抑制异常”(再次强调,异常不是崩溃的关键吗?)
好吧,Apple 的措辞具有误导性。您通常不应该“抑制”异常,除非在非常特殊的情况下您确定它们是无害的。如果您希望您的应用程序在任何异常时中止,您仍然应该编写异常处理程序。它可能只包含一些日志记录(您可以使用-[NSApp reportException:]
),然后终止您的应用程序 - 可能通过调用 abort()。 “通常”这是由默认的未处理异常处理程序完成的,但您不能依赖通过 NSOperation 代码传递的异常 - 它明确告诉您不要这样做。
嗯。好吧,这开始更有意义了。我可以在主线程上引发异常,而不是调用 abort() 吗?在@catch 块中会像[exception performSelectorOnMainThread:@selector(raise) ...]
这样工作吗? (动机是单元测试)【参考方案2】:
我认为this post 和随附的答案很好地阐述了一般异常 - 与无异常处理主题!
在资源不足的情况下抛出异常是不安全的 不是自动管理的。这是 Cocoa 框架的情况 (和邻居框架),因为它们使用手动引用计数。
如果您抛出异常,您会通过展开跳过任何释放调用 堆栈将导致泄漏。这应该限制你只投掷 如果你确定你不会恢复,因为所有资源 进程退出时返回给操作系统。
不幸的是,NSRunLoops 倾向于捕获所有传播的异常 给他们,所以如果你在一个事件中扔,你会继续到下一个 事件。这显然是非常糟糕的。因此,您最好 干脆别扔。
如果你使用垃圾收集的 Objective-C,这个问题就会减少, 因为任何由 Objective-C 对象表示的资源都将正确 释放。但是,C 资源(例如文件描述符或 malloc 分配的内存)未包装在 Objective-C 对象中 仍然会泄漏。
所以,总而言之,不要扔。
正如您所提到的,Cocoa API 有几个解决方法。 返回 nil 和 NSError** 模式是其中的两个。
【讨论】:
以上是关于为啥 NSOperation 示例代码使用@try & @catch的主要内容,如果未能解决你的问题,请参考以下文章