Is there a way for XCode to warn about new API cal

2019-01-03 08:32发布

On more than one occasion I've seen crashing bugs appear on iOS 3.x due to use of a new call that was introduced in 4.x without proper checking.

Is there a way for XCode to warn about classes, methods and procedures that are only available a later version than the deployment target?

That way I could easily list through all the code and make sure it's properly conditionalized.

9条回答
Juvenile、少年°
2楼-- · 2019-01-03 09:10

Latest Xcode didn't work with other answers. This works for me (only looking for UIKit issues).

The reason is that the newer clang versions have a built-in availability attribute.

#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>
查看更多
闹够了就滚
3楼-- · 2019-01-03 09:11

To get this to work under XCode 5 you need to also redefine the NS_AVAILABLE and NS_DEPRECATED macros because CFAvailability.h distinguishes between compilers that support the attribute_availability_with_message feature. Copy the following above the "MJGAvailability.h" import in your precompiled header to get this to work with the new Apple LLVM compiler:

#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__)
查看更多
Lonely孤独者°
4楼-- · 2019-01-03 09:12

If you use XCode7.3 and above, you can set other warning flag: -Wpartial-availability, then xcode will show warning for API newer than deployment target version enter image description here

查看更多
聊天终结者
5楼-- · 2019-01-03 09:14

I've actually released something which helps with testing this sort of thing. It's part of my MJGFoundation set of class called MJGAvailability.h.

The way I've been using it is to apply it in my PCH file like this:

#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>

Then it'll warn (with perhaps a strange deprecation warning) about APIs which are being used that are too new for the target you set as the "soft max" as per the #define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED. Also if you don't define __IPHONE_OS_VERSION_SOFT_MAX_REQUIRED then it defaults to your deployment target.

I find it useful because I can then double check which APIs I'm using that are too new for the deployment target that I've set.

查看更多
乱世女痞
6楼-- · 2019-01-03 09:16

it's not integrated into the toolset. one option to test this is to just create a runtime check which would assert (during development while running in newer versions of the os).

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

then just add that to one of your library's initialization routines.

you could also create a script which would count the number of warnings emitted for a specific translation - if the warning count in question changes then you have updates to make.

查看更多
乱世女痞
7楼-- · 2019-01-03 09:28

This is based on Ben S's answer, but incorporates support for GCC and LLVM-GCC. GCC's deprecated attribute doesn't take a message argument like clang's, so passing one produces a compiler error in basically every file.

Place the following code at the top of your ProjectName-Prefix.pch file to get a warning for every use of an API that may not be available in all your targeted versions:

#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

As Ben says, if you're intentionally doing this (perhaps by checking for the selector at runtime), you can hide the warning using this construct:

#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"

Regrettably, you can't do this inside a function, at least in i686-apple-darwin10-llvm-gcc-4.2 (GCC) 4.2.1.

查看更多
登录 后发表回答