【问题标题】:Is there a way for Xcode to warn about new API calls?Xcode 有没有办法警告新的 API 调用?
【发布时间】:2011-06-08 05:46:48
【问题描述】:

我不止一次看到 iOS 3.x 上出现崩溃错误,原因是使用了 4.x 中引入的新调用而没有经过适当检查。

Xcode 有没有办法警告仅在比部署目标更高的版本中可用的类、方法和过程?

这样我可以轻松地列出所有代码并确保它被适当地条件化。

【问题讨论】:

标签: iphone xcode ios iphone-sdk-3.0 warnings


【解决方案1】:

我实际上已经发布了一些有助于测试这类东西的东西。它是我的 MJGFoundation 类集的一部分,名为 MJGAvailability.h

我一直在使用它的方式是将它应用到我的 PCH 文件中,如下所示:

#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED __IPHONE_4_0
#import "MJGAvailability.h"

// The rest of your prefix header as normal
#import <UIKit/UIKit.h>

然后它会警告(可能是一个奇怪的弃用警告)正在使用的 API 对于您根据#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED 设置为“软最大值”的目标来说太新了。此外,如果您未定义 __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED,则默认为您的部署目标。

我发现它很有用,因为我可以仔细检查我使用的哪些 API 对于我设置的部署目标来说太新了。

【讨论】:

  • 我试过这个,但它给出的错误与我在 cmets 中对 Ben S 的回答相同。
  • @nevanking 真奇怪。它对我有用,没问题。这甚至意味着在 Xcode 的代码完成中查看方法时被划掉了,这也很方便!
  • @nevanking 我遇到了同样的问题,但这是因为我只是从 matt 的标题中获取了一些代码,而忘记了这一重要行:#define __AVAILABILITY_TOO_NEW __attribute__((deprecated("TOO NEW!") ))
  • 这个答案对我有用,而接受的答案在 Xcode 4.5 中没有
  • @BenC.R.Leggiero:请参阅 new answer below 关于使用 -Wpartial-availability 的“其他警告标志”来代替
【解决方案2】:

如果你使用 XCode7.3 及以上版本,你可以设置其他警告标志:-Wpartial-availability,然后 xcode 将显示比部署目标版本更新的 API 警告

【讨论】:

    【解决方案3】:

    至少在 OS X 上,使用最近的 clang/SDK,现在有一个 -Wpartial-availability 选项(例如在“其他警告选项”中添加它) 如果支持该方法,则可以定义以下宏来封装处理运行时测试的代码

    #define START_IGNORE_PARTIAL _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpartial-availability\"")
    #define END_IGNORE_PARTIAL _Pragma("clang diagnostic pop")
    

    我还没有在 iOS 上测试过。

    【讨论】:

    • 它适用于 Xcode 7。但是,引用项目中的警告不会显示。我必须将目标一一更改才能看到警告。
    • 这应该是现在公认的答案,因为 Clang 可以本机发出您需要的警告。如果您启用了“将警告视为错误”(您应该!),那么构建将不会成功。无需重新定义 CF 和 NS 宏。
    • 这是完美的答案。
    【解决方案4】:

    在挖掘AvailabilityInternal.h 之后,我意识到部署目标之上的所有可用版本都用__AVAILABILITY_INTERNAL_WEAK_IMPORT 宏标记。

    因此,我可以通过重新定义该宏来生成警告:

    #import <Availability.h>
    #undef  __AVAILABILITY_INTERNAL_WEAK_IMPORT
    #define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
        __attribute__((weak_import,deprecated("API newer than Deployment Target.")))
    

    通过将此代码放置在项目的预编译标头中,任何可能导致在受支持的最低 iOS 版本上崩溃的 API 的使用现在都会生成警告。如果您正确地保护了呼叫,您可以禁用专门针对该呼叫的警告(从 Apple 的 SDK Compatibility Guide 修改的示例):

    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
        if ([UIPrintInteractionController class]) {
            // Create an instance of the class and use it.
        }
    #pragma GCC diagnostic warning "-Wdeprecated-declarations"
        else {
            // Alternate code path to follow when the
            // class is not available.
        }
    

    【讨论】:

    • 我尝试将您的代码放入我的.pch 文件中,但在我构建时它立即在 Apple 框架中产生了大量错误。主要是“指定的参数数量错误”错误。 Xcode 4.2 为 iOS 3.1.3 构建。
    • 这在 LLVM-GCC 上效果很好只要您将 #define 行更改为: #define __AVAILABILITY_INTERNAL_WEAK_IMPORT \ __attribute__((weak_import,deprecated)))GCC doesn't take an argument 到它的 deprecated 属性,所以这个答案的代码将导致构建失败。
    • clang 添加了一个更优雅的“可用”属性——我发现最简单的方法就是绕过它(见下面我的答案)
    【解决方案5】:

    这基于Ben S's 答案,但包含对 GCC 和 LLVM-GCC 的支持。 GCC 的 deprecated 属性 doesn't take a message argument 就像 clang 的一样,因此传递一个基本上会在每个文件中产生编译器错误。

    将以下代码放在您的 ProjectName-Prefix.pch 文件的顶部,以便在每次使用可能并非在所有目标版本中都可用的 API 时收到警告:

    #import <Availability.h>
    #undef  __AVAILABILITY_INTERNAL_WEAK_IMPORT
    #ifdef __clang__
    #define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
    __attribute__((weak_import,deprecated("API newer than Deployment Target.")))
    #else
    #define __AVAILABILITY_INTERNAL_WEAK_IMPORT \
    __attribute__((weak_import,deprecated))
    #endif
    

    正如 Ben 所说,如果您有意这样做(可能是通过在运行时检查选择器),您可以使用此构造隐藏警告:

    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
        - (void)conditionallyUseSomeAPI {
            // Check for and use the appropriate API for this iOS version
        }
    #pragma GCC diagnostic warning "-Wdeprecated-declarations"
    

    很遗憾,您不能在函数内部执行此操作,至少在 i686-apple-darwin10-llvm-gcc-4.2 (GCC) 4.2.1 中是这样。

    【讨论】:

      【解决方案6】:

      要使其在 XCode 5 下工作,您还需要重新定义 NS_AVAILABLE 和 NS_DEPRECATED 宏,因为 CFAvailability.h 区分了支持 attribute_availability_with_message 功能的编译器。在您的预编译头文件中复制“MJGAvailability.h”导入上方的以下内容,以使其与新的 Apple LLVM 编译器一起使用:

      #import <Availability.h>
      #import <Foundation/NSObjCRuntime.h>
      
      #undef CF_AVAILABLE
      #undef CF_AVAILABLE_MAC
      #undef CF_AVAILABLE_IOS
      #undef CF_DEPRECATED
      #undef CF_DEPRECATED_MAC
      #undef CF_DEPRECATED_IOS
      #undef CF_ENUM_AVAILABLE
      #undef CF_ENUM_AVAILABLE_MAC
      #undef CF_ENUM_AVAILABLE_IOS
      #undef CF_ENUM_DEPRECATED
      #undef CF_ENUM_DEPRECATED_MAC
      #undef CF_ENUM_DEPRECATED_IOS
      
      #undef NS_AVAILABLE
      #undef NS_AVAILABLE_MAC
      #undef NS_AVAILABLE_IOS
      #undef NS_DEPRECATED
      #undef NS_DEPRECATED_MAC
      #undef NS_DEPRECATED_IOS
      #undef NS_ENUM_AVAILABLE
      #undef NS_ENUM_AVAILABLE_MAC
      #undef NS_ENUM_AVAILABLE_IOS
      #undef NS_ENUM_DEPRECATED
      #undef NS_ENUM_DEPRECATED_MAC
      #undef NS_ENUM_DEPRECATED_IOS
      #undef NS_AVAILABLE_IPHONE
      #undef NS_DEPRECATED_IPHONE
      
      #undef NS_CLASS_AVAILABLE
      #undef NS_CLASS_DEPRECATED
      #undef NS_CLASS_AVAILABLE_IOS
      #undef NS_CLASS_AVAILABLE_MAC
      #undef NS_CLASS_DEPRECATED_MAC
      #undef NS_CLASS_DEPRECATED_IOS
      
      //CF macros redefinition
      #define CF_AVAILABLE(_mac, _ios) __OSX_AVAILABLE_STARTING(__MAC_##_mac, __IPHONE_##_ios)
      #define CF_AVAILABLE_MAC(_mac) __OSX_AVAILABLE_STARTING(__MAC_##_mac, __IPHONE_NA)
      #define CF_AVAILABLE_IOS(_ios) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_##_ios)
      
      #define CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_##_macIntro, __MAC_##_macDep, __IPHONE_##_iosIntro, __IPHONE_##_iosDep)
      #define CF_DEPRECATED_MAC(_macIntro, _macDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_##_macIntro, __MAC_##_macDep, __IPHONE_NA, __IPHONE_NA)
      #define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA, __MAC_NA, __IPHONE_##_iosIntro, __IPHONE_##_iosDep)
      
      #define CF_ENUM_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
      #define CF_ENUM_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
      #define CF_ENUM_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)
      
      #define CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
      #define CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
      #define CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
      
      //NS macros redefinition
      #define NS_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
      #define NS_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
      #define NS_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)
      
      #define NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
      #define NS_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
      #define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
      
      #define NS_ENUM_AVAILABLE(_mac, _ios) CF_ENUM_AVAILABLE(_mac, _ios)
      #define NS_ENUM_AVAILABLE_MAC(_mac) CF_ENUM_AVAILABLE_MAC(_mac)
      #define NS_ENUM_AVAILABLE_IOS(_ios) CF_ENUM_AVAILABLE_IOS(_ios)
      
      #define NS_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, __VA_ARGS__)
      #define NS_ENUM_DEPRECATED_MAC(_macIntro, _macDep, ...) CF_ENUM_DEPRECATED_MAC(_macIntro, _macDep, __VA_ARGS__)
      #define NS_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_ENUM_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
      
      #define NS_AVAILABLE_IPHONE(_ios) CF_AVAILABLE_IOS(_ios)
      #define NS_DEPRECATED_IPHONE(_iosIntro, _iosDep) CF_DEPRECATED_IOS(_iosIntro, _iosDep)
      
      #define NS_CLASS_AVAILABLE(_mac, _ios) __attribute__((visibility("default"))) NS_AVAILABLE(_mac, _ios)
      #define NS_CLASS_DEPRECATED(_mac, _macDep, _ios, _iosDep, ...) __attribute__((visibility("default"))) NS_DEPRECATED(_mac, _macDep, _ios, _iosDep, __VA_ARGS__)
      
      #define NS_CLASS_AVAILABLE_IOS(_ios) NS_CLASS_AVAILABLE(NA, _ios)
      #define NS_CLASS_AVAILABLE_MAC(_mac) NS_CLASS_AVAILABLE(_mac, NA)
      #define NS_CLASS_DEPRECATED_MAC(_macIntro, _macDep, ...) NS_CLASS_DEPRECATED(_macIntro, _macDep, NA, NA, __VA_ARGS__)
      #define NS_CLASS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) NS_CLASS_DEPRECATED(NA, NA, _iosIntro, _iosDep, __VA_ARGS__)
      

      【讨论】:

      • 嗯,按照您在 Xcode 5.0.2 上的说明后,我仍然没有看到构建警告。
      • MJGAvailability.h 绝对是一个很好的起点。但是,对于较新的 Xcode/SDK(至少是 Xcode 7),如果启用了模块,它将无法工作(因为前缀标头不会更改带有模块的内置标头中的任何定义)
      【解决方案7】:

      它没有集成到工具集中。对此进行测试的一种选择是仅创建一个运行时检查,该检查将断言(在开发期间,同时在较新版本的操作系统中运行)。

      assert([<CLASS> instancesRespondToSelector:@selector(potato)]);
      

      然后只需将其添加到您的库的初始化例程之一。

      您还可以创建一个脚本来计算针对特定翻译发出的警告数量 - 如果相关警告数量发生变化,那么您需要进行更新。

      【讨论】:

      • 是的,这适用于您知道需要检查哪些 API 的情况。问题是在没有这些检查的情况下无意使用了 4.x 功能。
      • 嗨,Ben,我了解这个问题,谢谢。这些只是测试的几种方法-如何将问题最小化。一个没有明显解决方案的问题。因此,当您添加您认为将来可能由苹果引入的方法时添加检查会很有用 - 不仅在您知道要检查哪些 api 时。同样,您可以通过在说明 api 更改/添加的发行说明可用时进行反测试来对您的子类进行测试。它并不完美,但至少在配置后会持续自动化。
      【解决方案8】:

      最新的 Xcode 不适用于其他答案。这对我有用(只寻找 UIKit 问题)。

      原因是较新的 clang 版本具有内置的可用性属性。

      #define TESTING_COMPILATION_TARGET
      // only enable when trying to diagnose what APIs are being inappropriately used
      #ifdef TESTING_COMPILATION_TARGET
      #import <Availability.h>
      
      #define __MYUNSUPPORTED __attribute((deprecated("API version unsupported")))
      
      #define __MYUNSUPPORTED_IOS_NA __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_2_0
      #define __MYUNSUPPORTED_IOS_2_1
      #define __MYUNSUPPORTED_IOS_2_2
      #define __MYUNSUPPORTED_IOS_3_0
      #define __MYUNSUPPORTED_IOS_3_1
      #define __MYUNSUPPORTED_IOS_3_2
      #define __MYUNSUPPORTED_IOS_4_0
      #define __MYUNSUPPORTED_IOS_4_1
      #define __MYUNSUPPORTED_IOS_4_2
      #define __MYUNSUPPORTED_IOS_4_3
      #define __MYUNSUPPORTED_IOS_5_0
      #define __MYUNSUPPORTED_IOS_5_1
      #define __MYUNSUPPORTED_IOS_6_0
      #define __MYUNSUPPORTED_IOS_6_1
      #define __MYUNSUPPORTED_IOS_7_0
      #define __MYUNSUPPORTED_IOS_7_1 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_8_0 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_8_1 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_8_2 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_8_3 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_8_4 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_9_0 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_9_1 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_9_2 __MYUNSUPPORTED
      #define __MYUNSUPPORTED_IOS_9_3 __MYUNSUPPORTED
      
      #import <Foundation/Foundation.h>
      
      #undef CF_AVAILABLE
      #define CF_AVAILABLE(_mac, _ios) __MYUNSUPPORTED_IOS_##_ios
      
      #undef NS_AVAILABLE
      #define NS_AVAILABLE(_mac, _ios) __MYUNSUPPORTED_IOS_##_ios
      
      #undef CF_AVAILABLE_IOS
      #define CF_AVAILABLE_IOS(_ios) __MYUNSUPPORTED_IOS_##_ios
      
      #undef NS_AVAILABLE_IOS
      #define NS_AVAILABLE_IOS(_ios) __MYUNSUPPORTED_IOS_##_ios
      
      #endif // testing
      
      #import <UIKit/UIKit.h>
      

      【讨论】:

        【解决方案9】:

        不,没有这样的警告。但是,当您使用新 API 时(因为您显然是稍后再编写这些),只需在可用时查看文档即可。

        另外,如果您支持 3.0 并使用新的 SDK 进行开发,那么您绝对必须在运行 3.0 的实际设备上进行测试

        您可以做的另一件事是编写自己的实用程序来解析标头中的可用性宏,然后在您调用任何不应该调用的内容时发出警告。

        但是,我必须重申,如果您针对的是旧版本并使用较新的 SDK,则必须查看文档以了解 API 何时可用,并进行适当的测试。

        【讨论】:

        • 查找每个 API 调用并不是一个合理的解决方案。我确实同意 3.0 测试是必要的,但我正在寻找一种节省时间的解决方案,其中 4.x 代码被尽早捕获而不是在测试中。
        • @Ben:了解您正在编写的 API 是/绝对/合理的事情。作为专业人士,您应该知道您正式支持的系统的 API 是如何工作的,以及哪些是可用的,哪些是不可用的。所有这些都记录在案,当您 /start/ 包含新 API 时,您可以查找它。如果您不是专业人士或只是做一些业余爱好,那么根本不支持旧版本的操作系统。我对这个问题的回答也没有错或不好(恕我直言),这是开发人员在任何情况下的期望。此外,答案仍然是答案:没有这样的警告。
        • [NSThread currentThread] setPriority:1.0] 看起来很无害。听起来或看起来不像是仅限 4.0 的 API,但确实如此。 API 差异中的一些细微更改/添加与新类或框架无关。它们只是 MacOS X 多年来提供的新内容。牢记所有这些并不是“专业的”,而是无用的记忆,并且在我键入时或在我将代码检查到版本控制之前查找每个 API 调用是一个时间槽。作为一名专业人士,我知道何时引入框架/类,我想抓住这些小问题。
        • @Ben:这些东西你不必熟记,这就是文件在那里的原因。如果您以前从未调用过 setThreadPriority:,那么作为专业人士,您应该检查一下。此外,该调用 / 在 Mac OS X 中已存在多年 /。它是在 10.6 中引入的,在此之前不可用。基本上,iOS 3.0 和 3.1 从 10.5 开始遵循 Foundation,而 4.0(3.2 是一种特殊情况)从 10.6 开始遵循 Foundation。当新版本发布时,很容易查看 API 差异以查看旧类是否有细微更改。
        • 我不明白你为什么坚持我们应该记住每个 API 被引入的操作系统的想法,并且每次访问标题或文档都是乏味的。我们不是在谈论第一次使用。 Jason,您是否已将 4.3 中引入的每个 API 都提交到内存中?这是编译器可以根据您的部署目标进行的非常容易的检查。当然,编译器警告是不合适的,因为您可以对方法的存在进行运行时检查,但这将是方便的静态分析器添加。
        猜你喜欢
        • 2010-09-16
        • 2012-02-23
        • 2013-10-07
        • 1970-01-01
        • 2011-09-11
        • 1970-01-01
        • 2010-12-22
        • 2022-12-06
        • 2010-10-06
        相关资源
        最近更新 更多