【问题标题】:Suppressing "'…' is deprecated" when using respondsToSelector使用 respondsToSelector 时抑制“'...' is deprecated”
【发布时间】:2010-12-26 11:58:35
【问题描述】:

我通过在运行时选择最新的 API 来支持 10.4+:

if ([fileManager respondsToSelector:@selector(removeItemAtPath:error:)])
    [fileManager removeItemAtPath:downloadDir error:NULL];
else
    [fileManager removeFileAtPath:downloadDir handler:nil];

在这种情况下,10.5 及更高版本将使用 removeItemAtPath:error:,而 10.4 将使用 removeFileAtPath:handler:。很好,但我仍然收到旧方法的编译器警告:

warning: 'removeFileAtPath:handler:' is deprecated [-Wdeprecated-declarations]

是否有 if([… respondsToSelector:@selector(…)]){ … } else { … } 的语法提示编译器 (Clang) 不在该行发出警告?

如果没有,有没有办法标记 -Wdeprecated-declarations 忽略该行?


看到一些答案后,让我澄清一下,将编译器混淆为不知道我在做什么并不是一个有效的解决方案。

【问题讨论】:

    标签: objective-c cocoa xcode clang


    【解决方案1】:

    我在 Clang 编译器用户手册中找到了 an example,它让我忽略了警告:

    if ([fileManager respondsToSelector:@selector(removeItemAtPath:error:)]) {
        [fileManager removeItemAtPath:downloadDir error:NULL];
    } else {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        [fileManager removeFileAtPath:downloadDir handler:nil];
    #pragma clang diagnostic pop
    }
    

    【讨论】:

    • 如果 OS X 上的 Clang 不支持 push,您能否将调用封装在 #pragma clang diagnostic ignored "-Wdeprecated-declarations"#pragma clang diagnostic warning "-Wdeprecated-declarations" 中?
    • @sidnious 你能告诉我如何知道警告的具体名称吗?我的意思是,它显示警告“ is deprecated”,但我们使用“-Wdeprecated-declarations”。如何找到我们需要在 clang 诊断中使用的实际名称
    • 那个例子现在似乎从 Clang 编译器用户手册中消失了。
    【解决方案2】:

    您可以声明一个指定用于调用已弃用方法的单独文件,并将 Xcode 中的每个文件编译器标志设置为忽略 -Wdeprecated-declarations。然后,您可以在该文件中定义一个虚拟函数来调用已弃用的方法,从而避免在您的真实源文件中出现警告。

    【讨论】:

    • 好主意,特别是因为它将所有已弃用的 API 调用集中在一个地方。我将兼容性代码移到了一个单独的文件中,将-compatibility* 方法添加到相关类的类别中(例如,-[NSFileManager compatibleRemoveItemAtPath:])。
    【解决方案3】:

    我不确定 clang 是否足够聪明以捕捉到这一点,但如果不是,您可以尝试使用 performSelector:withObject:withObject: 或构建和调用 NSInvocation 对象。

    【讨论】:

    • 当您不确定它们是否存在时,performSelector: 和 kin 是在运行时调用 Objective-C 方法的正确解决方案。
    【解决方案4】:

    您可以将 fileManager 转换为 idids 能够引用任何 Objective-C 对象,因此编译器不应该检查在一个对象上调用的方法:

    [(id)fileManager removeItemAtPath:downloadDir error:NULL];
    

    不应引发任何警告或错误。

    当然,这会引发其他问题——即,您会丢失所有id 上调用的方法的编译时检查。所以如果你拼错了你的方法名等,直到那行代码被执行后才会被捕获。

    【讨论】:

    【解决方案5】:

    如果您认为任何形式的“混淆”编译器都是无效的解决方案,那么您可能不得不忍受警告。 (在我的书中,如果你问如何摆脱一个警告,那是不明智的

    在运行时有效的答案涉及屏蔽动态调度发生的操作,因此编译器不会抱怨已弃用的调用。如果您不喜欢这种方法,您可以在您的 Xcode 项目或目标设置中关闭“警告已弃用函数”,但这通常是个坏主意。您想了解已弃用的 API,但在这种情况下,您想在没有警告的情况下使用它。有一些简单和困难的方法可以做到这一点,而且您可能会认为所有这些方法都以某种形式“无效”,但这并不妨碍它们有效,甚至是正确的。 ;-)

    避免警告但仍然在运行时选择的一种可能方法是直接使用objc_msgSend()

    objc_msgSend(fileManager, @selector(removeFileAtPath:error:), downloadDir, nil];
    

    This is what the Objective-C runtime does under the covers anyway,并且应该以最少的麻烦完成您想要的结果。为了清楚起见,您甚至可以将原始行注释在其上方。我知道文档说,“编译器生成对消息传递函数的调用。你永远不应该在你编写的代码中直接调用它。”你必须独自决定什么时候可以改变规则。

    【讨论】:

    • 代码应该说明它的含义。如果没有,那么 API、语言或程序员就坏了。如果当程序员确信他的代码是正确的时编译器发出警告,那么编译器需要了解这种情况并自动忽略它,或者它需要支持一种语法,让它忽略该行上的特定警告(就像我发布的在 Xcode 附带的 Clang 中不起作用的语法)。其他任何东西都是 hack。
    • 我为你的理想主义喝彩,但有时你不得不妥协。我发布的代码以及其他人发布的代码确实说明了它的含义。我相信您的意思是编译器应该能够完全基于代码来判断您的意图,而无需跳过箍。这很好,但是您如何建议编译器应该知道您只会在运行时调用未弃用的给定方法?没有额外的语义信息,它真的不能。可以传达这种含义的语法会很棒,但在它存在之前,将其他所有内容称为 hack 是徒劳的。
    • 有时编写对普通观察者来说不是很清楚的代码是不可避免的。如有必要,这是添加澄清评论的最佳时机。使用performSelector:withObject:withObject:objc_msgSend() 是最小限度的破坏性方法,仍然可以传达您的意思并避免警告。 Java 有注释来抑制警告,但 C 没有。可惜了,但生活还要继续。我并不是在为当前的状态辩护,只是建议如果你要实际使用该语言,接受它的怪癖比成为精英更有效。
    • 回答你的问题:编译器已经有一个不推荐使用的方法列表,为什么不给它一个替代方法列表呢?然后,如果它看到一个已弃用的调用,它可能会查看它是否在测试 [<OBJECT> respondsToSelector:@selector(<ALTERNATIVE>))] 的条件的 else 分支内。
    • 我理解您的观点,但我认为这些答案与您目前所期望的一样好。弃用/替代方法方法是一个有趣的想法,但需要对将代码标记为弃用的语法和语义进行根本性更改。我更喜欢编译器检查的安全性,并避免转换为 id 只是为了避免警告。然而,虚假警告比偶尔的黑客攻击更能分散我的注意力,特别是当我进行单元测试以验证代码是否正常工作时。 :-) FWIW,我同意每行警告抑制。
    猜你喜欢
    • 1970-01-01
    • 2016-02-28
    • 2022-12-28
    • 1970-01-01
    • 1970-01-01
    • 2019-06-27
    • 2022-06-30
    • 2022-12-15
    • 1970-01-01
    相关资源
    最近更新 更多