Xcode是否有办法警告新的API调用?

28

我曾多次见过由于在iOS 3.x上使用了在4.x中引入但未经适当检查的新调用而出现崩溃性错误。

Xcode是否有一种方法可以警告在部署目标版本之后才可用的类、函数和过程?

这样,我就可以轻松地列出所有代码并确保它们被正确地条件化了。


要获取Mac OS X的明确解决方案,请查看http://lapcatsoftware.com/articles/sdkvsdeploymenttarget.html。 - SayeedHussain
9个回答

27

我实际上发布了一些有助于测试这种情况的东西。它是我的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>

接着,它会警告(可能会有一个奇怪的弃用警告),关于你所设置的“软件最大版本”(#define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED)对于正在使用的 API 来说过于新。此外,如果您没有定义 __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED,那么它会默认为您的部署目标。

我认为这很有用,因为我可以仔细检查我正在使用哪些 API,以确保它们不超出我设置的部署目标的范围。


2
这个答案对我有用,而被接受的答案在Xcode 4.5中不起作用。 - borrrden
在头文件CFAvailability.h中似乎有一个switch语句来检查具有attribute_availability_with_message支持的编译器。这个技巧似乎不能用于支持此功能的编译器。例如,我尝试使用iOS 7方法[UIViewController setNeedsStatusBarAppearanceUpdate]和将iOS部署目标设置为iOS 6.0来获得警告,但在XCode 5.0下似乎无法工作。有什么建议吗? - Werner Altewischer
我已经找到了一种解决我在上面评论中描述的问题的方法。请查看下面的回复:(https://dev59.com/1W445IYBdhLWcg3w1tgS#19704587) - Werner Altewischer
2
@BenC.R.Leggiero:请参阅下面的新答案,了解如何使用“其他警告标志”中的-Wpartial-availability - user102008
@mattjgalloway - 你的头文件在Xcode 8和iOS 9.3中无法工作。 - Duck
显示剩余9条评论

19
如果您使用XCode7.3及以上版本,您可以设置其他警告标志:-Wpartial-availability,然后XCode将会对新于部署目标版本的API显示警告信息。 在此输入图片描述

16

至少在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中可以工作。然而,在引用的项目中的警告并没有显示出来。我必须逐个更改目标才能看到这些警告。 - keithyip
现在应该接受这个答案,因为Clang可以本地发出所需的警告。如果启用了“将警告视为错误”(应该!),则构建将无法成功。无需重新定义CF和NS宏。 - Matthew
这是完美的答案。 - tutu_magi

13

在查阅了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 兼容性指南):

#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文件中,但是在构建时它立即在苹果框架中创建了大量错误。主要是“指定的参数数量错误”。Xcode 4.2构建iOS 3.1.3。 - nevan king
1
只要您将“#define”行更改为以下内容,“这在LLVM-GCC上非常有效”:#define __AVAILABILITY_INTERNAL_WEAK_IMPORT \ __attribute__((weak_import,deprecated)))GCC不会对其“deprecated”属性接受参数,因此本文的代码将导致构建失败。 - Phil Calvin
1
clang添加了一个更优雅的“available”属性 - 我发现最简单的方法是绕过它(请参见下面的答案)。 - xtravar

4
为了在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__)

2
哦,按照您在Xcode 5.0.2上的指示操作后,我仍然没有看到构建警告。 - Mike
MJGAvailability.h 绝对是一个很好的起点。然而,在新的 Xcode/SDK(至少是 Xcode 7)中,如果启用了模块化,它将无法工作(因为前缀头文件不会更改任何内置头文件中的定义)。 - Daniel

4
这是基于 Ben S 的 答案,但加入了对 GCC 和 LLVM-GCC 的支持。GCC的 deprecated 属性不像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中,您无法在函数内部执行此操作。


0

最新的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>

0

它没有集成到工具集中。测试的一个选项是创建一个运行时检查,该检查将在开发过程中断言(在较新版本的操作系统中运行时)。

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

然后将其添加到您的库初始化程序之一即可。

您还可以创建一个脚本,用于计算特定翻译发出的警告数量 - 如果涉及的警告计数发生更改,则需要进行更新。


是的,当您知道需要检查哪些API时,这很有效。问题在于没有进行这些检查就无意中使用了4.x功能。 - Ben S
嗨,本,我理解了这个问题,谢谢。这些只是测试的几种方式 - 如何逐步减少问题。这是一个没有明显解决方案的问题。因此,在添加您认为未来可能由苹果引入的方法时,添加检查将非常有用 - 不仅在您知道要检查哪些API时。同样,当发布说明提供API更改/添加时,您可以通过反向测试来对子类执行测试。它不是完美的,但一旦配置,至少是持续自动化的。 - justin

-4

不,没有这样的警告。然而,当你使用新的API(因为显然你是在后来编写这些代码)时,只需查看文档以获取可用性。

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

另一件事情是你可以编写自己的实用程序,解析头文件中的可用性宏,并在调用任何不应该调用的内容时发出警告。

然而,我必须再次强调,如果你的目标是较旧的版本并且使用较新的SDK,你必须查看文档以了解API何时可用,并进行适当的测试。


3
查阅每个API调用并不是一个合理的解决方案。我同意需要进行3.0版本的测试,但我所寻求的是一个节省时间的解决方案,能够在测试之前就能够捕捉到4.x版本的问题。请注意保持原意,让内容更通俗易懂。 - Ben S
1
@Ben:了解你正在编写的API是非常合理的。作为一名专业人士,你应该知道你正式支持的系统的API如何工作以及可用性。所有这些都有文档记录,当你开始包含新的API时,你可以查阅它们。如果你不是专业人士或只是做一些爱好工作,那么就不要支持旧版本的操作系统。在任何情况下,我的回答也不是错误或不好的(在我看来),这是开发人员在任何情况下都应该做到的。此外,答案仍然是答案:没有这样的警告。 - Jason Coco
8
[NSThread currentThread] setPriority:1.0] 看起来毫不起眼,没有像是只有在 4.0 版本才有的 API,但事实确实如此。API diffs 中一些较小的更改/添加与新的类或框架无关,它们只是从 MacOS X 多年来可用的内容中新增的。将这些全部记住并不是“专业”行为,这只是无用的死记硬背,而在每次打代码或提交版本控制之前查找每个 API 调用是浪费时间的做法。作为一个专业人士,我知道哪些框架/类何时引入,我想要抓住这些小小的坑点调用。 - Ben S
1
@Ben:你不必死记硬背这些东西,因为有文档在那里。如果你以前从未调用过setThreadPriority:,作为一名专业人士,你应该去查一下。此外,这个调用在Mac OS X中已经消失多年了。它是在10.6中引入的,在此之前不可用。基本上,iOS 3.0和3.1遵循来自10.5的Foundation,而4.0(3.2是一种特殊情况)则遵循来自10.6的Foundation。当新版本发布时,非常容易查看API差异,以查看旧类是否有轻微更改。 - Jason Coco
3
我不明白为什么你坚持认为我们应该记住每个API是在哪个操作系统中引入的,而每次都去参考头文件或文档只是很繁琐。我们并不是在谈论初次使用。Jason,你是否已经把4.3版本中引入的每个API都牢记于心?这很容易可以由编译器根据你的部署目标来检查。当然,编译器警告并不合适,因为你可能有运行时检查方法是否存在的代码,但这对于静态分析器的增加是非常方便的。 - Bryan Henry
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接