【问题标题】:Objective-C ExceptionsObjective-C 异常
【发布时间】:2011-06-06 15:27:43
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: objective-c exception-handling nserror


    【解决方案1】:

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

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

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

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

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

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

    关于 ARC 的说明

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

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

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

    这一切都来自ARC specification on the LLVM website

    【讨论】:

    • 释放@finally 块的对象怎么样? (或者是不是太不雅致导致代码过多?)
    • @Adrian Smith 在@finally 中发布在您自己的方法中很好。但是,如果您的方法的异常传播到它们,调用您的方法可能有也可能没有 @finally 块来清理。
    • 您必须依靠所有框架代码帧也正确使用@finally 或其他机制来释放所有对象。是的,这太乏味和太多代码了。它还会产生不小的开销。
    • 实际上问题并没有随着 GC 消失。除了内存之外还有其他资源需要释放。
    • @bbum 上次我从 clang 检查程序集时(大约一年前),@finally 块的内容只是附加到每个可能需要它的代码路径。
    【解决方案2】:

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

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

    void a() {
        @throw [MyException exceptionWithName: @"OhNoes!"  ....];
    }
    
    @try {
        ... call into framework code that calls a() above ...
    } @catch (MyException e) {
        ... handle e ...
    }
    

    底线:

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

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


    终于found the document我在找:

    重要提示:您应该保留使用权 编程异常或 意外的运行时错误,例如 越界集合访问, 尝试改变不可变对象, 发送无效消息,并丢失 与窗口服务器的连接。 你通常会照顾这些 出现异常时的错误数 正在创建应用程序 比运行时。

    如果您有现有的代码体 (例如第三方库) 使用异常处理错误 条件,您可以按原样使用代码 在您的 Cocoa 应用程序中。但是你 应确保任何预期 运行时异常不会从 这些子系统并最终在 调用者的代码。例如,一个解析 库可能使用异常 内部指示问题和 启用解析的快速退出 可以深度递归的状态; 但是,您应该注意抓住 顶级的此类例外 图书馆并将它们翻译成 适当的返回码或状态。

    【讨论】:

    • 好的,所以在 Objective-C 中,我们不会对常规条件使用异常。问它们是否可以在不花费太多成本的情况下用于异常情况(意外情况等),或者是否有大量开销(速度/内存)?当然,我问的是抛出异常概率低的地方。
    • @Yar 抛出异常的代价实际上是微不足道的,因为您应该只在程序偏离轨道到无法恢复的程度时才抛出异常。所以,不,一般来说,不需要担心开销,因为应用程序无论如何都会崩溃。
    • 谢谢。所以@catch 块本身的成本如果没有抛出异常 几乎为零,对吗?
    • 对--它的影响应该很小。
    【解决方案3】:

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

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

    另见

    【讨论】:

    • 我完全同意你的观点,即使可以应用异常处理——在 Objective-C 和 Cocoa 的上下文中这也是一个糟糕的决定——因为它们是动态的。
    【解决方案4】:

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

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

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

    【讨论】:

    • 不过,这种方法的问题在于重构。一旦您重构代码以便通过框架代码抛出异常,您还必须重构异常处理以在返回到框架之前捕获异常,然后在调用方重新抛出异常。随着时间的推移,这被证明是一个巨大的维护难题。
    • @bbum:但是你不会重构代码,以便通过框架代码抛出异常,因为那将是一个永恒的接口。
    • 不确定您的评论是什么意思;重构的问题在于,如果您更改代码以将异常繁重的类放入 Apple 提供的集合类中,则必须沿边界进行重构以防止异常从 -performSelector: 或-enumerateWithBlock:
    • @bbum:确实如此。但是作为公共 API 一部分的任何选择器都不应该引发异常(除非明确记录),如果其他人正在使用您的私有 API,他们应该得到他们所得到的。
    • 我不同意。当应用于多级代码和第 3 方代码时,异常不仅不是“更干净”,而且是明显的死胡同。它们在像 Obj-C 这样的动态语言中的运行时影响(当堆栈展开时)极大地影响了性能。使用保留计数、方法调配和 'id' 泛型编程,几乎不可能将异常处理合理地应用于 Cocoa 应用程序
    【解决方案5】:

    所使用的错误处理类型取决于您要完成的什么。几乎所有 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
    }
    

    希望这会有所帮助。

    【讨论】:

    • 不是 @try/@catch 块的常见用途。事实上,那行代码的行为是未定义的。异常用于指示不可恢复的程序员错误,不应用于尝试从错误状态恢复或用于流控制。
    • @bbum:你有这方面的资料,还是你个人的看法?此外,根据文档,它会引发异常:developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
    • 有据可查; developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/… 它抛出异常并不意味着该异常是要被捕获和恢复的。 iOS/Cocoa 中的异常意味着检测到不可恢复的程序员错误;这表明您的代码中存在错误。
    • 想想你在说什么。 ALL 异常旨在被捕获和分析以确定是否它们可以从中恢复。否则,操作系统为什么会首先扔掉它们?此外,抛出的异常并不总是表明代码中存在错误。它们可能是内部框架不一致或您无法控制的库的结果。
    • Cocoa/iOS 中的异常不是意味着要从中恢复(除了 2 或 3 个漏掉的方法——NSDictionary 的 setObject:forKey: 是 不是其中之一)。它们表示程序员错误。将它们放在首位是为了让应用有机会“优雅地失败”,而不是简单地消失。
    【解决方案6】:

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

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

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

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

    【讨论】:

      【解决方案7】:

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

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

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

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

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

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

      【讨论】:

      • 您不能在 Cocoa / iOS 编程中使用异常除非您确保跨堆栈帧集抛出异常,这些堆栈帧仅由您控制的代码组成。通过框架的异常是未定义的行为。
      • @bbum 我不确定这是对我的回应的批评,还是只是一个额外的事实。首先,我建议在第三段中让客户避免使用例外,同时一般不鼓励他们使用。
      • 只是为了澄清事实。
      猜你喜欢
      • 1970-01-01
      • 2013-03-25
      • 2014-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-09
      • 2012-02-17
      • 2010-09-24
      相关资源
      最近更新 更多