57

我刚刚完成了一个 iPhone 应用程序编程课程。作为课程的一部分,我看到

  • Objective-C 使用@try指令提供异常处理
  • 系统库不使用异常处理,宁愿return nil

我问我是否应该对我编写的新代码使用异常处理(例如,如果我同时编写前端和逻辑代码,那么它们之间的通信就在我手中)但我被告知不,不应该为新代码使用异常代码。(但他没有详细说明,然后上课继续,我想也许稍后会明白原因..)

例外肯定优于return nil? 您可以捕获特定类型,您不会试图通过忽略通常成功的函数的返回类型来忽略它们,您有可以记录的文本消息,它们允许您的代码专注于正常情况,因此更具可读性。为什么要使用异常

所以你怎么看?我的培训师有权不使用 Objective-C 异常吗?如果是这样,为什么?

4

7 回答 7

69

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

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

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

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

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

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

对 ARC 的说明

ARC 用户可以选择启用或禁用完全异常安全。启用异常安全后,ARC 将生成代码以在其作用域被终止时释放强引用,从而可以安全地在代码中使用异常。ARC 不会修补外部库以在其中启用异常支持,因此即使在程序中启用了异常支持,您也应该小心抛出的位置(尤其是捕获的位置)。

可以使用 启用-fobjc-arc-exceptions或禁用ARC 异常支持-fno-objc-arc-exceptions。默认情况下,它在 Objective-C 中是禁用的,但在 Objective-C++ 中是启用的。

默认情况下,Objective-C 中的完全异常安全是禁用的,因为 Clang 作者假设 Objective-C 程序无论如何都不会从异常中恢复,并且因为与清理相关的代码大小成本和性能损失很小。另一方面,在 Objective-C++ 中,C++ 已经引入了大量的清理代码,人们更可能需要异常安全。

这全部来自LLVM 网站上的 ARC 规范

于 2011-01-10T16:54:04.987 回答
36

在 Cocoa 和 iOS 编程中,异常用于指示不可恢复的程序员错误。当框架抛出异常时,它表明框架检测到一个错误状态,该状态既不可恢复,而且内部状态现在未定义。

同样,通过框架代码抛出的异常会使框架处于未定义状态。即你不能做类似的事情:

void a() {
    @throw [MyException exceptionWithName: @"OhNoes!"  ....];
}

@try {
    ... call into framework code that calls a() above ...
} @catch (MyException e) {
    ... handle e ...
}

底线:

异常不能在 Cocoa 中用于流控制、用户输入验证、数据有效性检测或以其他方式指示可恢复的错误。为此,您使用此处记录NSError**的模式(感谢 Abizem)。

(请注意,有少数 API 确实违反了这一点——异常用于指示可恢复状态。已针对这些提交了错误以弃用并最终消除这些不一致。)


终于找到了我要找的文件:

重要提示:您应该将异常用于编程或意外的运行时错误,例如越界集合访问、尝试改变不可变对象、发送无效消息以及失去与窗口服务器的连接。您通常在创建应用程序时而不是在运行时处理这些类型的异常错误。

如果您有使用异常来处理错误条件的现有代码主体(例如第三方库),您可以在 Cocoa 应用程序中按原样使用代码。但是您应该确保任何预期的运行时异常不会从这些子系统中逃逸并最终出现在调用者的代码中。例如,解析库可能会在内部使用异常来指示问题并允许快速退出可能深度递归的解析状态;但是,您应该注意在库的顶层捕获此类异常并将它们转换为适当的返回代码或状态。

于 2011-01-10T16:52:45.600 回答
8

我认为,如果我错了,其他人会纠正我,应该使用异常来捕获程序员错误,而NSError类型错误处理应该用于程序运行时发生的异常情况。

至于返回nil,这还不是全部——可能有问题的函数不只是返回 a nil,它们还可以(并且应该)使用作为参数传入的 NSError 对象提供更多信息。

也可以看看

于 2011-01-10T16:33:24.590 回答
3

就个人而言,我认为没有理由不在您自己的代码中使用异常。异常模式比处理错误更干净

  • 总是有返回值,
  • 确保可能的返回值之一真正意味着“错误”
  • 传递一个额外的参数,该参数是对NSError*
  • 为每个错误返回使用清理代码或使用明确的 goto 跳转到常见的错误清理部分。

但是,您需要记住,其他人的代码并不能保证正确处理异常(如果是纯C,实际上它无法正确处理异常)。因此,您绝不能允许异常传播到您的代码之外。例如,如果您在委托方法的内部深处抛出异常,则必须在返回委托消息的发送者之前处理该异常

于 2011-01-10T17:16:33.633 回答
2

使用的错误处理类型取决于要完成的任务。几乎所有 Apple 的方法都会填充一个NSError可以在出错时访问的对象。

这种类型的错误处理可以与一段代码中的 try-catch 块一起使用:

-(NSDictionary *)getDictionaryWithID:(NSInteger)dictionaryID error:(NSError **)error
{
    @try
    {
         //attempt to retrieve the dictionary
    }
    @catch (NSException *e)
    {
        //something went wrong
        //populate the NSError object so it can be accessed
    }
}

简而言之,该方法的目的决定了您应该使用的错误处理类型。但是,在上面的示例中,您可以同时使用两者。

try-catch 块的常见用途:

@try
{
    [dictionary setObject:aNullObject forKey:@"Key"];
}
@catch (NSException *e)
{
    //one of the objects/keys was NULL
    //this could indicate that there was a problem with the data source
}

希望这可以帮助。

于 2011-01-10T16:42:07.167 回答
1

我认为你的教练是正确的。您可以提出各种支持和反对异常的论据,但最重要的是,如果您希望您的代码对有经验的 Cocoa 开发人员有“气味”,您将使用 Apple 在其代码中使用的相同设计模式来实现您的应用程序,并且在他们的框架中。这意味着nils 和NSErrors 而不是异常。

因此,不使用异常的优势主要在于一致性和熟悉度。

要考虑的另一件事是,nilObjective C 中的 a 通常是相当“安全的”。也就是说,您可以向 a 发送任何消息,nil并且您的应用程序不会崩溃。

(我还应该指出,Apple确实在某些地方使用了异常,通常是在您错误地使用 API 的地方。)

于 2011-01-10T16:44:17.923 回答
0

如果您更喜欢例外,那么您可以使用它们。如果你这样做,我建议你谨慎使用它们,因为它变得非常难以维护(额外的退出点,你通常必须在运行时练习以证明程序的正确性)。

没有针对客户的异常处理期望的规范;他们必须根据文档维护他们的程序(繁琐,容易出错,可能在抛出异常之前不会维护)。

如果我要使用它们,我只会在极少数情况下使用它们,并尽可能使用错误代码或返回 nil。另外,我会在库内部使用它们,而不是将客户端暴露给接口中的异常(或副作用)。我不在 objc 或 c++ 中使用它们(好吧,我会抓住它们,但我不会扔掉它们)。

正如您通常所期望的那样,您应该避免使用它们来控制程序流(一种常见的误用)。

当您必须逃避某种崩溃时,最好使用它们。如果您现在要继续编写代码,我建议您只编写接口来处理错误作为程序流程的一部分,并尽可能避免编写异常。

对原始帖子的更正:可可库更喜欢返回 nil,在某些情况下,它们会为您抛出异常(以捕获)。

于 2011-01-10T17:02:34.800 回答